(function(angular) {
	'use strict';
	var defaultInputClass='input-sm', 
	defaultGridClass = 'col-sm-12',
	defaultTooltipPlacement='top-right', 
	defaultTooltipTemplateUrl='fs/layout/template/tooltipTpl.html', 
	defaultTooltipClass='input-tooltip';
	
	angular.module("fs.layout", [ 'fs.logger', 'fs.validation', 'pascalprecht.translate', 'ui.bootstrap' ])
	.directive("fsTable", fsTable)
	.directive("fsInnerList", fsInnerList)
	.directive("fsList", fsList)
	.directive("fsAside", fsAside)
	.directive("fsGridLayout", fsGridLayout)
	.directive('fsGroup', fsGroup)
	.directive('fsGrid', fsGrid)
	.directive('fsColor', fsColor)
	.directive('fsLabel', fsLabel)
	.directive("fsLink", fsLink)
	.directive('fsImage', fsImage)
	.directive('fsText', fsText)
	.directive('fsPassword',fsPassword)
	.directive('fsRadio', fsRadio)
	.directive('fsCheckbox', fsCheckbox)
	.directive('fsCheckboxGroup', fsCheckboxGroup)
	.directive('fsDate', fsDate)
	.directive('fsTime', fsTime)
	.directive('fsDatetime', fsDatetime)
	.directive('fsCalendarDay', fsCalendarDay)
	.directive('fsTextarea', fsTextarea)
	.directive('fsSelect', fsSelect)
	.directive('fsMultipleSelect', fsMultipleSelect)
	.directive('fsTypeahead', fsTypeahead)
	.directive('fsSensitive', fsSensitive)
	.directive('fsMoney', fsMoney)
	.directive('fsNumber', fsNumber)
	.directive('fsPercentage', fsPercentage)
	.directive('fsLookupText', fsLookupText)
	.directive('fsLookupMultiple', fsLookupMultiple)
	.directive("fsButtons", fsButtons)
	.directive("fsButton", fsButton)
	.directive("fsGridFilter", fsGridFilter)
	.directive("fsGridPrefix", fsGridPrefix)
	.directive("fsFileUpload", fsFileUpload)
	.directive("fsUiGridCompare", fsUiGridCompare)
	.directive("fsTabset", fsTabset)
	.directive("fsTab", fsTab)
	.directive('fsTabHeadingTransclude', fsTabHeadingTransclude)
	.directive('fsTabContentTransclude', fsTabContentTransclude)
	.directive('fsDashboardPanel', fsDashboardPanel)
	.run(setupTemplate);
	
	
	//<div fs-tabset active="active" type="type" vertical="vertical" justified="justified"/>
	fsTabset.$inject = [ '$rootScope', '$q', '$log', '$parse', '$compile', '$templateCache', '$timeout' ];
	function fsTabset($rootScope, $q, $log, $parse, $compile, $templateCache, $timeout) {
		return {
			transclude : true,
			replace : true,
			scope : {},
			bindToController : {
				active : '=?',
				type : '@'
			},
			controller : 'UibTabsetController',
			controllerAs : 'tabset',
			templateUrl : function(element, attrs) {
				return attrs.templateUrl || 'fs/template/tabs/tabset.html';
			},
			link : function(scope, element, attrs) {
				scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
				scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
			}
		};
	}
	
	// <div fs-tab heading="heading" index="index" classes="classes" select="select" deselect="deselect"/>
	fsTab.$inject = [ '$rootScope', '$q', '$log', '$parse', '$compile', '$templateCache', '$timeout' ];
	function fsTab($rootScope, $q, $log, $parse, $compile, $templateCache, $timeout) {
		return {
			require : ['^fsTabset', '^^?form'],
			replace : true,
			templateUrl : function(element, attrs) {
				return attrs.templateUrl || 'fs/template/tabs/tab.html';
			},
			transclude : true,
			scope : {
				heading : '@',
				index : '=?',
				classes : '@?',
				onSelect : '&select', // This callback is called in contentHeadingTransclude
				// once it inserts the tab's content into the dom
				onDeselect : '&deselect'
			},
			controller : [ '$scope', function(scope) {
				// Empty controller so other directives can require being 'under' a tab
			}],
			controllerAs : 'tab',
			link : function(scope, elm, attrs, ctrls, transclude) {
				var tabsetCtrl = ctrls[0], topFormCtrl = getTopFormCtrl(ctrls[1]), timers = [];
				scope.$on("$destroy", function(event) {
					angular.forEach(timers, function(timer, index) {
						$timeout.cancel(timer);
					});
				});
				scope.disabled = false;
				tabsetCtrl.loaded = tabsetCtrl.loaded || [];
				tabsetCtrl.loaded[scope.index] = (tabsetCtrl.active == scope.index || (tabsetCtrl.active === undefined && scope.index == 0) || false);
				if (topFormCtrl) {
					topFormCtrl.tabLoaded = tabsetCtrl.loaded;
				}
				/*if (tabsetCtrl.loaded[scope.index] == false) {
					timers.push($timeout(function() {
						tabsetCtrl.loaded[scope.index] = true;
					}, 4000 + scope.index * 2000));
				}*/
				if (attrs.disable) {
					scope.$parent.$watch($parse(attrs.disable), function(value) {
						scope.disabled = !!value;
					});
				}
				if (angular.isUndefined(attrs.index)) {
					if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) {
						scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) {
							return t.index;
						})) + 1;
					} else {
						scope.index = 0;
					}
				}
				if (angular.isUndefined(attrs.classes)) {
					scope.classes = '';
				}
				scope.select = function(evt) {
					if (!scope.disabled) {
						var index;
						for (var i = 0; i < tabsetCtrl.tabs.length; i++) {
							if (tabsetCtrl.tabs[i].tab === scope) {
								scope.index = index = i;
								tabsetCtrl.loaded[index] = true;
								break;
							}
						}
						tabsetCtrl.select(index, evt);
					}
				};
				scope.$watch(function() {
					return tabsetCtrl.active;
				}, function(newValue) {
					tabsetCtrl.loaded[newValue] = true;
				});
				tabsetCtrl.addTab(scope);
				scope.$on('$destroy', function() {
					tabsetCtrl.removeTab(scope);
				});
				scope.$transcludeFn = transclude;
			}
		};
	}
	
	function fsTabHeadingTransclude() {
		return {
			restrict : 'A',
			require : '^fsTab',
			link : function(scope, elm) {
				scope.$watch('headingElement', function updateHeadingElement(heading) {
					if (heading) {
						elm.html('');
						elm.append(heading);
					}
				});
			}
		};
	}
	
	function fsTabContentTransclude() {
		return {
			restrict : 'A',
			require : '^fsTabset',
			controller : [ '$scope', '$element', '$attrs', function(scope, elm, attrs) {
				this.getTabIndex = function() {
					var tab = scope.$eval(attrs.fsTabContentTransclude).tab;
					return tab.index;
				};
			}],
			link : function(scope, elm, attrs) {
				var tab = scope.$eval(attrs.fsTabContentTransclude).tab;
				// Now our tab is ready to be transcluded: both the tab heading area
				// and the tab content area are loaded. Transclude 'em both.
				tab.$transcludeFn(tab.$parent, function(contents) {
					angular.forEach(contents, function(node) {
						if (isTabHeading(node)) {
							// Let tabHeadingTransclude know.
							tab.headingElement = node;
						} else {
							elm.append(node);
						}
					});
				});
			}
		};

		function isTabHeading(node) {
			return node.tagName
					&& (node.hasAttribute('uib-tab-heading') || node.hasAttribute('data-uib-tab-heading') || node.hasAttribute('x-uib-tab-heading') || node.tagName.toLowerCase() === 'uib-tab-heading'
							|| node.tagName.toLowerCase() === 'data-uib-tab-heading' || node.tagName.toLowerCase() === 'x-uib-tab-heading' || node.tagName.toLowerCase() === 'uib:tab-heading');
		}
	}
	
	// <fs-inner-list ng-model="vm.company.table" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsInnerList.$inject = [ '$rootScope', '$q', '$log', '$parse', '$compile', '$templateCache', '$injector', '$timeout' ];
	function fsInnerList($rootScope, $q, $log, $parse, $compile, $templateCache, $injector, $timeout) {
		return {
			restrict : 'AE',
			scope : true,
			require : ['^fsGrid', 'fsInnerList'],
			controller : fsInnerListController,
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content;
				$element.addClass('fsInnerList');
				initComponent(ctrls[1], ctrls[0]);
				content = $templateCache.get('fs/layout/template/fsInnerList.html');
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
		fsInnerListController.$inject = [ '$scope', '$element', '$attrs', '$transclude', 'gridService' ];
		function fsInnerListController($scope, $element, $attrs, $transclude, gridService) {
			$scope.fsGridConfig.tableConfig.model = 'ngModel.' + $scope.fsGridConfig.model;
			$scope.fsGridConfig.gridOptions = gridService.createInnerGridOptions($scope.fsGridConfig.appScope, 
					$scope.fsGridConfig.tableConfig, 
					$scope.fsGridConfig.tableConfig.selection,
					$scope.fsGridConfig.tableConfig.apiBoundName);
			$scope.fsGridConfig.appScope[$scope.fsGridConfig.model + "GridOptions"] = $scope.fsGridConfig.gridOptions;
		}
	}
	
	// <fs-list ng-model="vm.company.table" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsList.$inject = [ '$rootScope', '$q', '$log', '$parse', '$compile', '$templateCache', '$injector', '$timeout' ];
	function fsList($rootScope, $q, $log, $parse, $compile, $templateCache, $injector, $timeout) {
		return {
			restrict : 'AE',
			scope : true,
			require : ['^fsGrid', 'fsList'],
			controller : fsListController,
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content;
				$element.addClass('fsInnerList');
				initComponent(ctrls[1], ctrls[0]);
				content = $templateCache.get('fs/layout/template/fsList.html');
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
		fsListController.$inject = [ '$scope', '$element', '$attrs', '$transclude', 'gridService' ];
		function fsListController($scope, $element, $attrs, $transclude, gridService) {
			$scope.fsGridConfig.tableConfig.model = 'ngModel.' + $scope.fsGridConfig.model;
			$scope.fsGridConfig.gridOptions = gridService.createGridOptions($scope.fsGridConfig.appScope, 
					$scope.fsGridConfig.tableConfig, 
					$scope.fsGridConfig.tableConfig.pagination, 
					$scope.fsGridConfig.tableConfig.selection,
					$scope.fsGridConfig.tableConfig.sort,
					$scope.fsGridConfig.tableConfig.apiBoundName);
			$scope.fsGridConfig.appScope[$scope.fsGridConfig.model + "GridOptions"] = $scope.fsGridConfig.gridOptions;
			
		}
	}
	
	// <div fs-table fs-config="tableOptions" fs-verify-model="verifyModel" fs-validator-config="validatorConfig"/>
	fsTable.$inject = [ '$rootScope', '$q', '$log', '$parse', '$compile', '$templateCache', '$injector', '$timeout' ];
	function fsTable($rootScope, $q, $log, $parse, $compile, $templateCache, $injector, $timeout) {
		return {
			restrict : 'AE',
			scope : true,
			require : ['^^?form', '^^?fsTabContentTransclude', 'fsTable'],
			controller : fsTableController,
			controllerAs : 'fsTableController',
			compile : function($element, $attrs) {
				$element.addClass('fsTable');
				return {
					pre : function($scope, $element, $attrs, ctrls, $transclude) {
						var formCtrl = ctrls[0], tabController = ctrls[1], fsTableCtrl = ctrls[2], watchers = [], watcher, watched, pageLoaded, content;
						var topFormCtrl = getTopFormCtrl(formCtrl);
						registerFormCtrls(topFormCtrl, formCtrl);
						$scope.$on('$destroy', function() {
							while (watchers.length) {
								(watchers.shift() || angular.noop)();
							}
						});
						watcher = $scope.$parent.$watch($attrs.fsConfig, function(gridOptions) {
							if (gridOptions && (fsTableCtrl.gridOptions != gridOptions || !angular.equals(fsTableCtrl.gridOptions, gridOptions))) {
								fsTableCtrl.gridOptions = gridOptions;
								if (!watched) {
									watched = true;
									watchers.push($scope.$watch(gridOptions.data, function(data) {
										if (topFormCtrl) {
											validateForm($scope, data, gridOptions, topFormCtrl, tabController, formCtrl);
											$rootScope.initFormTableState($scope, gridOptions, topFormCtrl.$name);
										}
										if (!pageLoaded && (!topFormCtrl || !topFormCtrl.formValidation)) {
											content = $templateCache.get('fs/layout/template/fsTable.html');
											content = content.replace(/TABLE_CONFIG/g, $attrs.fsConfig);
											content = $compile(angular.element(content))($scope);
											$element.append(content);
											pageLoaded = true;
										}
									}));
								}
								if (!gridOptions.prepared) {
									prepareFsConfig(gridOptions.appScope || $scope.$parent, gridOptions);
								}
							}
						});
						watchers.push(watcher);
						if (topFormCtrl) {
							topFormCtrl.saveValidateMessage = topFormCtrl.saveValidateMessage || function(formCtrl, validateResult) {
								if (formCtrl) {
									formCtrl.validateErrors = formCtrl.validateErrors || {};
									formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule] = validateResult.messages;
								}
							};
							topFormCtrl.removeValidateMessage = topFormCtrl.removeValidateMessage || function(formCtrl, validateResult) {
								if (formCtrl && formCtrl.validateErrors && formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule]) {
									delete formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule];
								}
							};
						}
					}
				};
			}
		};
		fsTableController.$inject = [ '$scope', '$element', '$attrs', '$transclude', 'ajaxServiceFactory' ];
		function fsTableController($scope, $element, $attrs, $transclude, ajaxServiceFactory) {
		}
		function prepareFsConfig(appScope, gridOptions) {
			if (gridOptions) {
				angular.forEach(gridOptions.columnDefs, function(columnDef) {
					columnDef.appScope = appScope;
					columnDef.gridClass = columnDef.gridClass || defaultGridClass;
					columnDef.inputClass = columnDef.inputClass || defaultInputClass;
					if (columnDef.tooltip) {
						columnDef.tooltip = angular.isArray(columnDef.tooltip) ? columnDef.tooltip : [ columnDef.tooltip ];
					}
					columnDef.tooltipClass = columnDef.tooltipClass || defaultTooltipClass;
					columnDef.tooltipClass = columnDef.tooltipClass || defaultTooltipClass;
					columnDef.tooltipPlacement = columnDef.tooltipPlacement || defaultTooltipPlacement;
					columnDef.tooltipTemplateUrl = columnDef.tooltipTemplateUrl || defaultTooltipTemplateUrl;
					if (columnDef.uppercase == undefined) {
						columnDef.uppercase = true;
					}
					if (columnDef.validatorConfig) {
						gridOptions.validatorConfig = columnDef.validatorConfig;
					}
					if (gridOptions.validatorConfig) {
						columnDef.validatorConfig = gridOptions.validatorConfig;
						prepareGridRules($parse, columnDef, columnDef.validatorConfig);
					}
				});
				gridOptions.prepared = true;
			}
		}
		function validateForm($scope, gridDatas, gridOptions, topFormCtrl, tabController, formCtrl) {
			var promise, promises = [], defered;
			if (!tabController || !gridOptions || !gridOptions.prepared || !topFormCtrl.formValidation) {
				return;
			}
			defered = topFormCtrl.formValidation[tabController.getTabIndex()] ? topFormCtrl.formValidation[tabController.getTabIndex()].defered : $q.defer();
			
			if (!gridDatas || gridDatas.length == 0 || !topFormCtrl.formValidation[tabController.getTabIndex()] 
			|| topFormCtrl.formValidation[tabController.getTabIndex()].validated != false 
			|| !gridOptions.validatorConfig) {
				topFormCtrl.formValidation[tabController.getTabIndex()] = null;
				topFormCtrl.tabLoaded[tabController.getTabIndex()] = false;
				defered.resolve();
				return;
			}
			topFormCtrl.formValidation[tabController.getTabIndex()] = null;
			angular.forEach(gridOptions.columnDefs, function(gridConfig) {
				if(gridConfig.rules){
					angular.forEach(gridConfig.rules.ruleInfos, function(rule) {
						var ruleName = angular.lowercase(rule.ruleName);
						if ("conditiondependent" != ruleName) {
							angular.forEach(gridDatas, function(formEntity) {
								var d = $q.defer(), validateResult = {
									'field' : gridConfig.field || gridConfig.model,
									'rule' : rule.ruleName,
									'messages' : null
								};
								rule.fieldLabel = gridConfig.label;
								rule.ngModel = formEntity;
								rule.operatedField = gridConfig.field || gridConfig.model;
								rule.modelType = gridConfig.displayName;
								promises.push(d.promise);
								$injector.get(ruleName).validate($parse(gridConfig.model)(formEntity), undefined, rule).then(function(e) {
									validateResult.invalid = false;
									topFormCtrl.removeValidateMessage(formCtrl, validateResult);
									d.resolve();
								}, function(e) {
									validateResult.invalid = true;
									validateResult.messages = e.error;
									topFormCtrl.saveValidateMessage(formCtrl, validateResult);
									d.resolve();
								});
							});
						}
					});
				}
			});
			$q.all(promises).then(function(e) {
				topFormCtrl.formValidation[tabController.getTabIndex()] = null;
				topFormCtrl.tabLoaded[tabController.getTabIndex()] = false;
				defered.resolve();
			});
		}
	}
	
	// <div fs-dashboard-panel fs-config="panelConfig"/>
	fsDashboardPanel.$inject = [ '$rootScope', '$q', '$log', '$parse', '$compile', '$templateCache', '$timeout', '$injector' ];
	function fsDashboardPanel($rootScope, $q, $log, $parse, $compile, $templateCache, $timeout, $injector) {
		function isJsonLike(str) {
		    var jsonStart = str.match(/^\[|^\{(?!\{)/);
		    return jsonStart && {'[': /]$/,'{': /}$/}[jsonStart[0]].test(str);
		}
		return {
			restrict : 'A',
			scope : false,
			require : 'fsDashboardPanel',
			controller : fsDashboardPanelController,
			controllerAs : 'fsDashboardPanelController',
			compile : function($element, $attrs) {
				$element.addClass('fsDashboardPanel');
				return {
					pre : function($scope, $element, $attrs, ctrl, $transclude) {
						var contentEL, content, watcher, watchers = [], log = $log.getInstance('fsDashboardPanel');
						var panelConfig = $scope.$eval($attrs.fsConfig);
						$scope.$on('$destroy', function() {
							while (watchers.length) {
								(watchers.shift() || angular.noop)();
							}
						});
//						contentEL = $templateCache.get('fs/layout/template/fsDashboardPanel.html');
//						contentEL = angular.element(contentEL);
//						content = $compile(contentEL)($scope);
//						// Prevent jQuery cache memory leak (template is now redundant after linking)
//						contentEL.remove();
//						$element.append(content);
						watcher = $scope.$watch(function(){
							return $element.hasClass('col-sm');
						}, function(inited, old) {
							if (inited && !old) {
								panelConfig.height = panelConfig.minHeight - 98;
								if (panelConfig.panelParams && isJsonLike(panelConfig.panelParams)) {
									panelConfig.panelParams = angular.fromJson(panelConfig.panelParams);
									panelConfig.width = Math.round(($element[0].offsetWidth- 73) * (panelConfig.panelParams.chartWidth || 1));
								} else {
									panelConfig.width = $element[0].offsetWidth - 73;
								}
								$injector.get('dashboardPanelService').addPanel(panelConfig);
							}
						});
						watchers.push(watcher);
					}
				};
			}
		};
		fsDashboardPanelController.$inject = [ '$scope', '$element', '$attrs', '$transclude', '$parse', 'ajaxServiceFactory' ];
		function fsDashboardPanelController($scope, $element, $attrs, $transclude, $parse, ajaxServiceFactory) {
		}
	}
	
	//<div fs-aside fs-config="asideConfig"/>
	fsAside.$inject = [ '$rootScope', '$q', '$log', '$parse', '$compile', '$templateRequest', '$exceptionHandler', '$timeout' ];
	function fsAside($rootScope, $q, $log, $parse, $compile, $templateRequest, $exceptionHandler, $timeout) {
		return {
			restrict : 'A',
			scope : false,
			require : '^^?form',
			controller : fsAsideController,
			controllerAs : 'fsAsideController',
			compile : function($element, $attrs) {
				$element.addClass('fsAside');
				return {
					pre : function($scope, $element, $attrs, formCtrl, $transclude) {
						var watchers = [], watcher, asideLeftWidth, asideRightWidth = 212;
						$scope.$on('$destroy', function() {
							while (watchers.length) {
								(watchers.shift() || angular.noop)();
							}
						});
						watcher = $scope.$watch('app.settings.collapseRightAside', function(collapseRightAside) {
							var height, uiGrid = $element[0].querySelector('.ui-grid');
							if ($scope.asideConfig && collapseRightAside === false && uiGrid && uiGrid.offsetWidth > asideRightWidth) { 
								asideLeftWidth = uiGrid.offsetWidth;
								height = uiGrid.style.height;
								uiGrid.removeAttribute("style");
								uiGrid = angular.element(uiGrid);
								uiGrid.css({height:height, width:(asideLeftWidth-asideRightWidth)+'px'});
								$timeout(function() {
									uiGrid[0].removeAttribute("style");
									uiGrid.css({height:height});
								}, 200);
							}
						});
						watchers.push(watcher);
						if ($attrs.fsAside) {
							watcher = $scope.$watch($attrs.fsAside, function(asideConfig) {
								if(asideConfig == $scope.asideConfig) return;
								$scope.asideConfig = asideConfig;
								if ($scope.asideConfig) { 
									$element.addClass("app-content-body-left");
									if($scope.asideConfig.templateUrl) {
										$scope.asideConfig.templateUrl = $scope.asideConfig.templateUrl.replace('${theme}', $scope.app.settings.theme);
									} 
								}
							});
							watchers.push(watcher);
						} else {
							$scope.asideConfig = $scope.$eval('vm.layoutConfig.asideConfig');
							if ($scope.asideConfig) {
								$element.addClass("app-content-body-left");
								if($scope.asideConfig.templateUrl) {
									$scope.asideConfig.templateUrl = $scope.asideConfig.templateUrl.replace('${theme}', $scope.app.settings.theme);
								}
							}
						}
						$templateRequest('waf/view/'+$scope.app.settings.theme+'/template/aside.html').then(function(content) {
							content = $compile(angular.element(content))($scope);
							$element.after(content);
				        });
					}
				};
			}
		};
		fsAsideController.$inject = [ '$scope', '$element', '$attrs', '$transclude', '$parse', 'ajaxServiceFactory' ];
		function fsAsideController($scope, $element, $attrs, $transclude, $parse, ajaxServiceFactory) {
			this.showHelp = function(help) {
				$scope.help = help;
			};
			this.removeHelp = function() {
				delete $scope.help;
			};
		}
	}

	// <fs-grid-layout ng-model="vm.company" fs-verify-model="vm.companyOld" fs-config="vm.layoutConfig" fs-validator-config="vm.validatorConfig"/>
	fsGridLayout.$inject = [ '$rootScope', '$q', '$log', '$parse', '$filter', '$compile', '$timeout', '$injector', 'formatService' ];
	function fsGridLayout($rootScope, $q, $log, $parse, $filter, $compile, $timeout, $injector, formatService) {
		// Initialization
		var log = $log.getInstance('gridLayout');
		function getAppScope(scope) {
			if (!scope) {
				return null;
			}
			if (scope.hasOwnProperty('vm')) {
				return scope;
			} else {
				return getAppScope(scope.$parent);
			}
		}
		return {
			restrict : 'EA',
			scope : true,
			bindToController : {
				fsValidatorConfig : '=fsValidatorConfig',
				fsVerifyModel : '=fsVerifyModel',
				ngModel : '=ngModel',
				fsConfig : '=fsConfig'
			},
			require : ['fsGridLayout', '^^?form', '^^?fsTabContentTransclude'],
			templateUrl : 'fs/layout/template/fsGridLayout.html',
			controller : fsGridLayoutController,
			controllerAs : 'fsGridLayoutController',
			compile: function ($element, $attrs) {
				// Compilation
				$element.addClass('fsGridLayout');
				$element.addClass($attrs.fsGridCalss);
		        return {
		          // Pre-link
		          pre: function($scope, $element, $attrs, ctrls, $transclude) {
						var watcher, watchers = [], fsGridLayoutController = ctrls[0], formCtrl = ctrls[1], fsTabCtrl = ctrls[2], template = '';
						var topFormCtrl = getTopFormCtrl(formCtrl);
						registerFormCtrls(topFormCtrl, formCtrl);
						$scope.$on('$destroy', function() {
							while (watchers.length) {
								(watchers.shift() || angular.noop)();
							}
						});
						watcher = $scope.$watch($attrs.fsVerifyModel, function(verifyModel) {
							if (!fsGridLayoutController.compareResult) {
								fsGridLayoutController.compareResult = verifyCompare(fsGridLayoutController, fsGridLayoutController.formContainer, fsGridLayoutController.ngModel,
										verifyModel);
							}
						});
						watchers.push(watcher);
						watcher = $scope.$watch($attrs.ngModel, function(ngModel) {
							$scope.ngModel = ngModel;
							if (topFormCtrl) {
								topFormCtrl._originalNgModel = {};
								topFormCtrl._originalNgModelKey = $attrs.ngModel;
								angular.copy(ngModel, topFormCtrl._originalNgModel);
								$rootScope.initFormState($scope, $attrs.ngModel, topFormCtrl.$name);
							}
							validateForm($scope, fsGridLayoutController, topFormCtrl, fsTabCtrl, formCtrl);
							if (!fsGridLayoutController.compareResult) {
								fsGridLayoutController.compareResult = verifyCompare(fsGridLayoutController, fsGridLayoutController.formContainer, ngModel,
										fsGridLayoutController.fsVerifyModel);
							}
						});
						watchers.push(watcher);
						watcher = $scope.$watch($attrs.fsConfig, function(fsConfig) {
							var formContainer;
							if (!fsConfig || ($scope.formContainer && 
									(fsConfig == fsGridLayoutController.fsConfig 
											|| angular.equals(fsConfig, fsGridLayoutController.fsConfig)))) {
								return;
							}
							formContainer = [];
							fsConfig.appScope = getAppScope($scope);
							fsGridLayoutController.fsConfig = fsConfig;
							buildGridGroups(fsConfig.appScope, formContainer, fsConfig.gridGroups || {}, fsConfig.gridGroupConfigs || {},fsConfig.gridConfigs || {}, 12, fsConfig.name || "__general__", fsGridLayoutController.fsValidatorConfig);
							fsConfig.prepared = true;
							validateForm($scope, fsGridLayoutController, topFormCtrl, fsTabCtrl, formCtrl);
							if (!topFormCtrl || !topFormCtrl.formValidation) {
								fsGridLayoutController.formContainer = formContainer;
								if (!fsGridLayoutController.compareResult) {
									fsGridLayoutController.compareResult = verifyCompare(fsGridLayoutController, fsGridLayoutController.formContainer, fsGridLayoutController.ngModel,
											fsGridLayoutController.fsVerifyModel);
								}
							}
						});
						watchers.push(watcher);
						if (topFormCtrl) {
							topFormCtrl.saveValidateMessage = topFormCtrl.saveValidateMessage || function(formCtrl, validateResult) {
								if (formCtrl) {
									formCtrl.validateErrors = formCtrl.validateErrors || {};
									formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule] = validateResult.messages;
								}
							};
							topFormCtrl.removeValidateMessage = topFormCtrl.removeValidateMessage || function(formCtrl, validateResult) {
								if (formCtrl && formCtrl.validateErrors && formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule]) {
									delete formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule];
								}
							};
						}
		          },
		          // Post-link
		          post: function($scope, $element, $attrs, formCtrl, $transclude) {
		          }
		        };
		    }
		};
		fsGridLayoutController.$inject = [ '$scope', '$element', '$attrs', '$transclude', '$parse', 'ajaxServiceFactory' ];
		function fsGridLayoutController($scope, $element, $attrs, $transclude, $parse, ajaxServiceFactory) {
			var ctrl = this;
			ctrl.isActive = function(grid) {
				var expression = grid ? grid.active : undefined;
				if (expression === undefined) {
					return true;
				}
				if(angular.isString(expression) && expression.indexOf('vm.') < 0){
					return evalBooleanExpression(expression, $scope.ngModel);
				}else{
					return evalBooleanExpression(expression, $scope);
				}
			};
			
			ctrl.isShow = function(grid) {
				var expression = grid ? grid.show : undefined;
				if(grid.model=='password'){
					grid;
				}
				if (expression === undefined) {
					return true;
				}
				if(angular.isString(expression) && expression.indexOf('vm.') < 0){
					return evalBooleanExpression(expression, $scope.ngModel);
				}else{
					return evalBooleanExpression(expression, $scope);
				}
			};
			ctrl.isReadonly = function(grid) {
				var expression = grid ? grid.readonly : undefined;
				if (expression === undefined) {
					return false;
				}
				if(angular.isString(expression) && expression.indexOf('vm.') < 0){
					return evalBooleanExpression(expression, $scope.ngModel);
				}else{
					return evalBooleanExpression(expression, $scope);
				}
			};
		}
		function evalBooleanExpression(expression, $scope){
			if (expression === true || expression === false) {
				return expression;
			}
			return $parse(expression)($scope);
		}
		function initGroupCollapsed(groupContainer, $scope) {
			var expression, getter;
			if (groupContainer.type == 'anonymous') {
				groupContainer.collapsed = function() {
					return false;
				};
				return;
			}
			expression = groupContainer.isCollapsed;
			if (expression == undefined) {
				groupContainer.isCollapsed = groupContainer.type == 'search' ? true : false;
			} else {
				groupContainer.isCollapsed = $parse(expression)($scope);
			}
			groupContainer.collapsed = function() {
				return groupContainer.isCollapsed;
			};
			groupContainer.toggleGroupCollapsed = function() {
				groupContainer.isCollapsed = !groupContainer.isCollapsed;
			};
			if (groupContainer.type == 'search') {
				$scope.$parent.toggleSearchGroupCollapsed = function() {
					return groupContainer.toggleGroupCollapsed();
				};
				$scope.$parent.isSearchCollapsed = function() {
					return groupContainer.collapsed();
				};
			}
			
		}
		function buildGridGroups($scope, container, groups, groupConfig, gridConfig, gridNumber, tabName, fsValidatorConfig) {
			var anonymousGroup;
			angular.forEach(groups, function(group) {
				if (angular.isArray(group)) { // anonymous group, just contains grids
					anonymousGroup = {
						name : 'anonymous',
						type : 'anonymous',
						grids : group,
						appScope : $scope
					};
					buildGridGroups($scope, container, [anonymousGroup], groupConfig, gridConfig, gridNumber, tabName);
				} else if (!groupConfig[group.name] || groupConfig[group.name].type === 'anonymous') { // anonymous group
					angular.extend(group, groupConfig[group.name]);
					group.type = group.type || 'anonymous';
					group.rows = group.rows || [];
					group.appScope = $scope;
					initGroupCollapsed(group, $scope);
					container.push(group);
					buildGrids($scope, group.rows, group.grids, groupConfig, gridConfig, gridNumber, group, tabName, fsValidatorConfig);
					if (group.gridGroup) {
						group.gridGroups = [];
						buildGridGroups($scope, group.gridGroups, group.gridGroup, groupConfig, gridConfig, gridNumber, tabName);
						delete group.gridGroup;
				    }
				} else { // normal group
					angular.extend(group, groupConfig[group.name]);
					group.type = group.type || 'normal';
					group.rows = group.rows || [];
					group.appScope = $scope;
					initGroupCollapsed(group, $scope);
					container.push(group);
					buildGrids($scope, group.rows, group.grids, groupConfig, gridConfig, gridNumber, group, tabName);
					if (group.gridGroup) {
						group.gridGroups = [];
						buildGridGroups($scope, group.gridGroups, group.gridGroup, groupConfig, gridConfig, gridNumber, tabName, fsValidatorConfig);
						delete group.gridGroup;
				    }
				}
			});
		}
		function verifyCompare(layoutCtrl, groupContainers, ngModel, verifyModel) {
			var value, oldValue;
			if (layoutCtrl.compareResult || !ngModel || !ngModel.primaryKey || !verifyModel || !verifyModel.primaryKey || !groupContainers || !groupContainers.length > 0) {
				return null;
			}
			var compareResult = {
				fields : {}
			};
			angular.forEach(groupContainers, function(groupContainer) {
				angular.forEach(groupContainer.rows, function(row) {
					angular.forEach(row, function(gridConfig) {
						if (gridConfig.filter) {
							value = $parse(gridConfig.model + ' | ' + gridConfig.filter)(ngModel);
							oldValue = $parse(gridConfig.model + ' | ' + gridConfig.filter)(verifyModel);
						} else {
							value = $parse(gridConfig.model)(ngModel);
							oldValue = $parse(gridConfig.model)(verifyModel);
						}
						
						if (!angular.equals(value, oldValue)) {
							compareResult.fields[gridConfig.model] = buildCompareResult($parse, $filter, formatService, layoutCtrl, gridConfig, value, oldValue);
						} else {
							delete compareResult.fields[gridConfig.model];
						}
					});
				});
				angular.merge(compareResult, verifyCompare(layoutCtrl, groupContainers.groups, ngModel, verifyModel));
			});
			return compareResult;
		}
		function setGridPosition(gridObj, gridNumber) {
			gridObj.size = (gridObj.size && gridObj.size > gridNumber) ? gridNumber : ((gridObj.size && gridObj.size > 0) ? gridObj.size : 12);
			gridObj.gridClass = 'col-sm-' + gridObj.size;
		}
		function buildGrids($scope, rows, grids, groupConfig, gridConfig, gridNumber, group, tabName, fsValidatorConfig) {
			var i = 0, row, gridObj;
			angular.forEach(grids, function(value, key) {
				if (angular.isString(value)) {// arrange grid by grid.size
					gridObj = gridConfig[value];
					if (!gridObj) {
						log.debug('can not found ' + value + ' config.');
						return;
					}
					gridObj.appScope = $scope;
					gridObj.tabName = tabName;
					//add this code for group readonly.
					if(gridObj.readonly === undefined && group && groupConfig[group.name] && groupConfig[group.name].readonly != undefined) {
						gridObj.readonly = groupConfig[group.name].readonly;
					}
					if(gridObj.active === undefined && group && groupConfig[group.name] && groupConfig[group.name].active != undefined) {
						gridObj.active = groupConfig[group.name].active;
					}
					setGridPosition(gridObj, gridNumber);
					if (i == 0) {
						row = [];
						rows.push(row);
					}
					i += gridObj.size;
					if (i > gridNumber && row.length > 0) {
						row = [ gridObj ];
						rows.push(row);
						i = gridObj.size;
					} else {
						row.push(gridObj);
					}
					prepareGridRules($parse, gridObj, fsValidatorConfig);
				} else if (angular.isArray(value)) {// force create a new row
					if (row) {
						row = null;
					}
					i = 0;
					buildGrids($scope, rows, value, groupConfig, gridConfig, gridNumber, group, tabName, fsValidatorConfig);
				}
			});
		}
		function validateForm($scope, fsGridLayoutController, topFormCtrl, tabController, formCtrl) {
			var formEntity = fsGridLayoutController.ngModel, fsConfig = fsGridLayoutController.fsConfig, promise, promises = [], defered, log = $log.getInstance('validateForm');
			if(!fsConfig){
				log.debug("validateForm cannot found the fsConfig!");
			}
			if (!tabController || !fsConfig || !fsConfig.prepared || !topFormCtrl.formValidation) {
				return;
			}
			defered = topFormCtrl.formValidation[tabController.getTabIndex()] ? topFormCtrl.formValidation[tabController.getTabIndex()].defered : $q.defer();
			if (!topFormCtrl.formValidation[tabController.getTabIndex()] 
			|| topFormCtrl.formValidation[tabController.getTabIndex()].validated != false 
			|| !fsGridLayoutController.fsValidatorConfig) {
				topFormCtrl.formValidation[tabController.getTabIndex()] = null;
				topFormCtrl.tabLoaded[tabController.getTabIndex()] = false;
				defered.resolve();
				return;
			}
			topFormCtrl.formValidation[tabController.getTabIndex()] = null;
			angular.forEach(fsGridLayoutController.fsConfig.gridConfigs, function(gridConfig) {
				if(gridConfig.rules){
					angular.forEach(gridConfig.rules.ruleInfos, function(rule) {
						var ruleName = angular.lowercase(rule.ruleName);
						if ("conditiondependent" != ruleName) {
							var d = $q.defer(), validateResult = {
								'field' : gridConfig.model,
								'rule' : rule.ruleName,
								'messages' : null
							};
							rule.fieldLabel = gridConfig.label;
							rule.ngModel = formEntity;
							rule.operatedField = gridConfig.field || gridConfig.model;
							rule.modelType = gridConfig.type;
							promises.push(d.promise);
							$injector.get(ruleName).validate($parse(gridConfig.model)(formEntity), undefined, rule).then(function(e) {
								validateResult.invalid = false;
								topFormCtrl.removeValidateMessage(formCtrl, validateResult);
								d.resolve();
							}, function(e) {
								validateResult.invalid = true;
								validateResult.messages = e.error;
								topFormCtrl.saveValidateMessage(formCtrl, validateResult);
								d.resolve();
							});
						}
					});
				}
			});
			$q.all(promises).then(function(e) {
				topFormCtrl.formValidation[tabController.getTabIndex()] = null;
				topFormCtrl.tabLoaded[tabController.getTabIndex()] = false;
				defered.resolve();
			});
		}
	}

	// <fs-group ng-model="vm.company" fs-verify-model="vm.companyOld" fs-group-config="groupObj"/>
	fsGroup.$inject = [ '$parse' ];
	function fsGroup($parse) {
		return {
			restrict : 'EA',
			scope : {},
			bindToController : {
				fsValidatorConfig : '=fsValidatorConfig',
				fsVerifyModel : '=fsVerifyModel',
				ngModel : '=ngModel',
				groupContainer : '=fsGroupConfig'
			},
			require : [ 'fsGroup', '^fsGridLayout' ],
			controller : function() {},
			controllerAs : 'fsGroupController',
			templateUrl : 'fs/layout/template/fsGroup.html',
			compile : function($element, $attrs) {
				$element.addClass('fsGroup');
				return {
					pre : function($scope, $element, $attrs, ctrls, $transclude) {
						var fsGroupController = ctrls[0];
						fsGroupController.searchCallback = function() {
							fsGroupController.groupContainer.appScope.searchCallback();
						};
						fsGroupController.isActive = function(gridObj) {
							if(gridObj) return ctrls[1].isActive(gridObj);
							return ctrls[1].isActive(fsGroupController.groupContainer);
						};
						fsGroupController.isShow = function(gridObj) {
							if(gridObj) return ctrls[1].isShow(gridObj);
							return ctrls[1].isShow(fsGroupController.groupContainer);
						};
					}
				};
			}
		};
	}

	//<fs-grid ng-model="vm.company" fs-verify-model="vm.companyOld" fs-grid-config="gridObj"/>
	fsGrid.$inject = [ '$rootScope', '$parse', '$compile', '$templateCache', 'formatService' ];
	function fsGrid($rootScope, $parse, $compile, $templateCache, formatService) {
		return {
			restrict : 'EA',
			scope : {
				fsValidatorConfig : '=fsValidatorConfig',
				fsVerifyModel : '=fsVerifyModel',
				ngModel : '=ngModel',
				fsGridConfig : '=fsGridConfig',
				uiGrid : '=fsUiGrid',
				uiGridRow : '=fsUiGridRow',
				row : '=fsUiGridRow',
				uiGridCol : '=fsUiGridCol'
			},
			require : ['^^?form','^^?uiGrid','^^?fsGridLayout', '^^?fsAside', 'fsGrid', '^^?fsUiGridCompareCtrl'],
			templateUrl : 'fs/layout/template/fsGrid.html',
			controller : fsGridController,
			controllerAs : 'fsGridController',
			compile: function ($element, $attrs) {
				$element.addClass('fsGrid');
				return {
					pre: function($scope, $element, $attrs, ctrls, $transclude) {
						var topFormCtrl, formCtrl, uiGridCtrl, fsGridLayoutCtrl, fsGridCtrl, asideCtrl, fsUiGridCompareCtrl, originalNgModel; 
						formCtrl = ctrls[0];
						uiGridCtrl = ctrls[1];
						fsGridLayoutCtrl = ctrls[2];
						asideCtrl = ctrls[3];
						fsGridCtrl = ctrls[4];
						fsUiGridCompareCtrl = ctrls[5];
						topFormCtrl = getTopFormCtrl(formCtrl);
						registerFormCtrls(topFormCtrl, formCtrl);
						if (topFormCtrl && uiGridCtrl && uiGridCtrl.grid && uiGridCtrl.grid.appScope && uiGridCtrl.grid.options && !topFormCtrl._originalNgModel) {
							originalNgModel = uiGridCtrl.grid.appScope.$eval(uiGridCtrl.grid.options.data);
							if (originalNgModel) {
								topFormCtrl._originalNgModel = [];
								topFormCtrl._originalNgModelKey = uiGridCtrl.grid.options.data;
								angular.copy(originalNgModel, topFormCtrl._originalNgModel);
								$rootScope.initFormState(uiGridCtrl.grid.appScope, uiGridCtrl.grid.options.data, topFormCtrl.$name);
							}
						}
						fsGridCtrl.isActive = function(grid) {
							var active, expression = grid ? grid.active : undefined;
							if (expression === undefined) {
								active = true;
							} else if ($scope.uiGridRow) {
								active = evalBooleanExpression(expression, $scope);
							} else {
								if(angular.isString(expression) && expression.indexOf('vm.') < 0){
									active = evalBooleanExpression(expression, $scope.ngModel);
								}else{
									active = evalBooleanExpression(expression, $scope.fsGridConfig.appScope);
								}
							}
							if (active === false) {
								if (grid.model) {
									if(formCtrl && formCtrl[grid.model] && formCtrl[grid.model].$invalid && formCtrl.validateErrors && grid.rules && grid.rules.ruleInfos){
										delete fsGridCtrl.validateResult[grid.model];
										angular.forEach(grid.rules.ruleInfos, function(rule) {
											delete formCtrl.validateErrors[grid.model + '.' + rule.ruleName];
										});
									}
									if ($scope.ngModel !== undefined && $scope.ngModel[grid.model] !== undefined) {
										if(grid.hasOwnProperty('defaultValue')){
											 $scope._hasSetValueFromDefaultValue = true;
									         $scope.ngModel[grid.model] = grid.defaultValue;
									    } else {
									    	delete $scope.ngModel[grid.model];
									    }
									}
								}
							} else if($scope._backupValue && $scope._backupValue.hasOwnProperty(grid.model)){
								$scope._hasSetValueFromDefaultValue = false;
							    $scope.ngModel[grid.model] = $scope._backupValue[grid.model];
						    }
							return active;
						};
						fsGridCtrl.isShow = function(grid) {
							var expression = grid ? grid.show : undefined;
							if (expression === undefined) {
								return true;
							} else if ($scope.uiGridRow) {
								return evalBooleanExpression(expression, $scope);
							} else {
								if(angular.isString(expression) && expression.indexOf('vm.') < 0){
									return evalBooleanExpression(expression, $scope.ngModel);
								}else{
									return evalBooleanExpression(expression, $scope.fsGridConfig.appScope);
								}
							} 
						};
						fsGridCtrl.isReadonly = function(grid) {
							var expression = grid ? grid.readonly : undefined;
							if (expression === undefined) {
								return false;
							} else if ($scope.uiGridRow) {
								return evalBooleanExpression(expression, $scope);
							} else {
								if(angular.isString(expression) && expression.indexOf('vm.') < 0){
									return evalBooleanExpression(expression, $scope.ngModel);
								}else{
									return evalBooleanExpression(expression, $scope.fsGridConfig.appScope);
								}
							} 
						};
						fsGridCtrl.focus = function() {
							if(asideCtrl) {
								asideCtrl.showHelp($scope.fsGridConfig.help);
							}
						};
						fsGridCtrl.blur = function() {
							if(asideCtrl) {
								asideCtrl.removeHelp();
							}
						};
						fsGridCtrl.isChanged = function () {
							if(uiGridCtrl) {
								if(!$scope.uiGridRow.compareResult || !$scope.uiGridRow.compareResult.fields[$scope.uiGridCol.colDef.model]) {
									return false;
								}
								return $scope.uiGridRow.compareResult.isCreated || $scope.uiGridRow.compareResult.isDeleted || $scope.uiGridRow.compareResult.fields[$scope.uiGridCol.colDef.model].hasChanged;
							} else if(fsGridLayoutCtrl) {
								if(!fsGridLayoutCtrl.compareResult || !$scope.fsGridConfig || !fsGridLayoutCtrl.compareResult.fields[$scope.fsGridConfig.model]) {
									return false;
								}
								return fsGridLayoutCtrl.compareResult.fields[$scope.fsGridConfig.model].hasChanged;
							}
						};
						fsGridCtrl.showErrors = function() {
							if ($scope.fsGridConfig && formCtrl && formCtrl[$scope.fsGridConfig.model] && formCtrl[$scope.fsGridConfig.model].$invalid && (formCtrl[$scope.fsGridConfig.model].$dirty || topFormCtrl.$submitted)) {
								return true;
							}
							return false;
						};
						fsGridCtrl.saveValidateMessage = function(validateResult) {
							fsGridCtrl.validateResult[$scope.fsGridConfig.model] = fsGridCtrl.validateResult[$scope.fsGridConfig.model] || {};
							fsGridCtrl.validateResult[$scope.fsGridConfig.model].messages = fsGridCtrl.validateResult[$scope.fsGridConfig.model].messages || {};
							fsGridCtrl.validateResult[$scope.fsGridConfig.model].messages[validateResult.rule] = validateResult.messages;
							if (formCtrl) {
								formCtrl.validateErrors = formCtrl.validateErrors || {};
								formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule] = validateResult.messages;
							}
						};
						fsGridCtrl.removeValidateMessage = function(validateResult) {
							if (fsGridCtrl.validateResult && fsGridCtrl.validateResult[$scope.fsGridConfig.model] && fsGridCtrl.validateResult[$scope.fsGridConfig.model].messages
									&& fsGridCtrl.validateResult[$scope.fsGridConfig.model].messages[validateResult.rule]) {
								delete fsGridCtrl.validateResult[$scope.fsGridConfig.model].messages[validateResult.rule];
							}
							if (formCtrl && formCtrl.validateErrors && formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule]) {
								delete formCtrl.validateErrors[validateResult.field + '.' + validateResult.rule];
							}
							if (topFormCtrl && topFormCtrl.validateErrors && topFormCtrl.validateErrors[validateResult.field + '.' + validateResult.rule]) {
								delete topFormCtrl.validateErrors[validateResult.field + '.' + validateResult.rule];
							}
						};
						fsGridCtrl.getTooltipMessage = function() {
							var tooltip, validateMessages = fsGridCtrl.getValidateMessages();
							if (validateMessages.length > 0) {
								tooltip = validateMessages;
							} else if(fsGridLayoutCtrl && fsGridLayoutCtrl.compareResult 
									&& fsGridLayoutCtrl.compareResult.fields 
									&& fsGridLayoutCtrl.compareResult.fields[$scope.fsGridConfig.model] 
									&& fsGridLayoutCtrl.compareResult.fields[$scope.fsGridConfig.model].tooltip) {
								tooltip = fsGridLayoutCtrl.compareResult.fields[$scope.fsGridConfig.model].tooltip;
							} else if($scope.uiGridRow && $scope.uiGridRow.compareResult 
									&& $scope.uiGridRow.compareResult.fields 
									&& $scope.uiGridRow.compareResult.fields[$scope.fsGridConfig.model] 
									&& $scope.uiGridRow.compareResult.fields[$scope.fsGridConfig.model].tooltip) {
								tooltip = $scope.uiGridRow.compareResult.fields[$scope.fsGridConfig.model].tooltip;
							} else if ($scope.fsGridConfig.tooltip) {
								tooltip = $scope.fsGridConfig.tooltip;
							} else {
								tooltip = null;
							}
							return tooltip;
						};
					},
					// Post-link
					post: function($scope, $element, $attrs, ctrls, $transclude) {
						var initialized = false, modelWatched = false, ngModelWatched = false, formCtrl, topFormCtrl, uiGridCtrl, fsGridCtrl, watcher, watchers = []; 
						formCtrl = ctrls[0];
						uiGridCtrl = ctrls[1];
						fsGridCtrl = ctrls[4];
						topFormCtrl = getTopFormCtrl(formCtrl);
						$scope.$on('$destroy', function() {
							while (watchers.length) {
								(watchers.shift() || angular.noop)();
							}
							destoryFormCtrls([topFormCtrl], formCtrl);
						});
						watcher = $scope.$watch('fsGridConfig', function(gridObj) {
							var callback;
							if(initialized) return;
							if (gridObj && gridObj.type) {
								initialized = true;
								if (uiGridCtrl && gridObj.label) {
									gridObj.showLabel = false;
								} else if (gridObj.label) {
									gridObj.showLabel = true;
								}
								$scope.fsGridConfig = gridObj;
								$scope.fsGridConfig.gridClass = $scope.fsGridConfig.gridClass || defaultGridClass;
								$scope.fsGridConfig.inputClass = $scope.fsGridConfig.inputClass || defaultInputClass;
								if ($scope.fsGridConfig.uppercase == undefined) {
									$scope.fsGridConfig.uppercase = true;
								}
								prepareGridTooltip($scope, fsGridCtrl);
								if (!$scope.fsGridConfig.hasPreparedGridRules) {
									prepareGridRules($parse, $scope.fsGridConfig, $scope.fsValidatorConfig);
								}
								if (!ngModelWatched) {
									ngModelWatched = true;
									if ($scope.fsGridConfig.dynamicParameter && $scope.fsGridConfig.defaultValue) {
										watchers.push($scope.$watch($parse('ngModel'), function(model, oldModel) {
											if ($scope.fsGridConfig.dynamicParameter && $scope.fsGridConfig.defaultValue) {
												var getter = $parse($scope.fsGridConfig.model);
												if(!getter($scope.ngModel)) {
													getter.assign($scope.ngModel, $scope.fsGridConfig.defaultValue);
												}
												prepareDynamicParameter($scope, ctrls[4]);
											}
										}));
									}
								}
								if (!modelWatched) {
									modelWatched = true;
									//callback = $scope.fsGridConfig.options ? $scope.fsGridConfig.options.callback : $scope.fsGridConfig.callback;
									//if (angular.isString($scope.fsGridConfig.options) || (callback && callback.changed)) {
										watchers.push($scope.$watch($parse('ngModel.' + $scope.fsGridConfig.model), function(model, oldModel) {
											var isInitialized  = false, options = $scope.fsGridConfig.options;
											if (model == oldModel) {
												isInitialized = true;
											}
											if (angular.isString(options)) {
												options = ((uiGridCtrl && uiGridCtrl.grid) ? uiGridCtrl.grid.appScope : $scope.fsGridConfig.appScope).$eval(options);
											}
											callback = options ? options.callback : $scope.fsGridConfig.callback;
											if (callback && angular.isString(callback.changed)) {
												callback = ((uiGridCtrl && uiGridCtrl.grid) ? uiGridCtrl.grid.appScope : $scope.fsGridConfig.appScope).$eval(callback.changed);
											}
											if(callback && angular.isFunction(callback)) {
												if (!$scope.ngModel && $scope.uiGridRow) {
													callback($scope.uiGridRow.entity, formCtrl, isInitialized);
												} else {
													callback($scope.ngModel, formCtrl, isInitialized);
												}
											}
											prepareDynamicParameter($scope, ctrls[4]);
											//back up value for grid witch has active property.
											if($scope._hasSetValueFromDefaultValue) return;
											if($scope.fsGridConfig.hasOwnProperty('active') && $scope.ngModel.hasOwnProperty($scope.fsGridConfig.model)){
												if(!$scope._backupValue) $scope._backupValue = {};
									            $scope._backupValue[$scope.fsGridConfig.model] = model;
								            }
										}));
									//}
									/*if ($scope.fsGridConfig.dynamicParameter) {
										watchers.push($scope.$watch($parse('ngModel.' + $scope.fsGridConfig.model), function(model) {
											prepareDynamicParameter($scope, ctrls[4]);
										}));
									}*/
								}
							}
						});
						watchers.push(watcher);
					}
				};
			}
		};
		fsGridController.$inject = [ '$rootScope', '$scope', '$element', '$attrs', '$transclude' ];
		function fsGridController($rootScope, $scope, $element, $attrs, $transclude) {
			var ctrl = this;
			ctrl.validateResult = {};
			ctrl.getValidateMessages = function() {
				var validateMessages = [];
				if(ctrl.validateResult && ctrl.validateResult[$scope.fsGridConfig.model] && ctrl.showErrors()) {
					angular.forEach(ctrl.validateResult[$scope.fsGridConfig.model].messages, function(message, rule) {
						if (message) {
							validateMessages.push(message);
						}
					});
				}
				return validateMessages;
			};
			ctrl.getGridEntity = function() {
				return $scope.ngModel;
			};
			ctrl.getGridAppScope = function() {
				return $scope.fsGridConfig.appScope;
			};
			ctrl.getGridTemplateUrl = function() {
				var templateUrl;
				if (!$scope.fsGridConfig) {
					templateUrl = '';
				} else if ($scope.fsGridConfig.templateUrl) {
					templateUrl = $scope.fsGridConfig.templateUrl;
				} else if ($scope.fsGridConfig.type == 'color') {
					templateUrl = 'fs/layout/template/color.html';
				} else if ($scope.fsGridConfig.type == 'label') {
					templateUrl = 'fs/layout/template/label.html';
				} else if ($scope.fsGridConfig.type == 'link') {
					templateUrl = 'fs/layout/template/link.html';
				} else if ($scope.fsGridConfig.type == 'image') {
					templateUrl = 'fs/layout/template/image.html';
				} else if ($scope.fsGridConfig.type == 'text') {
					templateUrl = 'fs/layout/template/text.html';
				} else if ($scope.fsGridConfig.type == 'password') {
					templateUrl = 'fs/layout/template/password.html';
				} else if ($scope.fsGridConfig.type == 'date') {
					templateUrl = 'fs/layout/template/date.html';
				} else if ($scope.fsGridConfig.type == 'time') {
					templateUrl = 'fs/layout/template/time.html';
				} else if ($scope.fsGridConfig.type == 'datetime') {
					templateUrl = 'fs/layout/template/datetime.html';
				} else if ($scope.fsGridConfig.type == 'calendarDays') {
					templateUrl = 'fs/layout/template/calendarDay.html';
				} else if ($scope.fsGridConfig.type == 'checkbox') {
					templateUrl = 'fs/layout/template/checkbox.html';
				} else if ($scope.fsGridConfig.type == 'checkbox-group') {
					templateUrl = 'fs/layout/template/checkboxGroup.html';
				} else if ($scope.fsGridConfig.type == 'radio') {
					templateUrl = 'fs/layout/template/radio.html';
				} else if ($scope.fsGridConfig.type == 'textarea') {
					templateUrl = 'fs/layout/template/textarea.html';
				} else if ($scope.fsGridConfig.type == 'sensitive') {
					templateUrl = 'fs/layout/template/sensitive.html';
				} else if ($scope.fsGridConfig.type == 'typeahead') {
					templateUrl = 'fs/layout/template/typeahead.html';
				} else if ($scope.fsGridConfig.type == 'select') {
					templateUrl = 'fs/layout/template/select.html';
				} else if ($scope.fsGridConfig.type == 'multiple-select') {
					templateUrl = 'fs/layout/template/multipleSelect.html';
				} else if ($scope.fsGridConfig.type == 'number') {
					templateUrl = 'fs/layout/template/number.html';
				} else if ($scope.fsGridConfig.type == 'percentage') {
					templateUrl = 'fs/layout/template/percentage.html';
				} else if ($scope.fsGridConfig.type == 'money') {
					templateUrl = 'fs/layout/template/money.html';
				} else if ($scope.fsGridConfig.type == 'file-upload') {
					templateUrl = 'fs/layout/template/fileUpload.html';
				} else if ($scope.fsGridConfig.type == 'lookup-text') {
					templateUrl = 'fs/layout/template/lookupText.html';
				} else if ($scope.fsGridConfig.type == 'lookup-image') {
					templateUrl = 'fs/layout/template/lookupImage.html';
				} else if ($scope.fsGridConfig.type == 'lookup-multiple') {
					templateUrl = 'fs/layout/template/lookupMultiple.html';
				} else if ($scope.fsGridConfig.type == 'button') {
					templateUrl = 'fs/layout/template/button.html';
				} else if ($scope.fsGridConfig.type == 'buttons') {
					templateUrl = 'fs/layout/template/buttons.html';
				} else if ($scope.fsGridConfig.type == 'innerTable') {
					templateUrl = 'fs/layout/template/innerList.html';
				} else if ($scope.fsGridConfig.type == 'table') {
					templateUrl = 'fs/layout/template/list.html';
				}
				return templateUrl;
			};
		}
		function prepareGridTooltip($scope, fsGridCtrl) {
			if ($scope.fsGridConfig.tooltip) {
				$scope.fsGridConfig.tooltip = angular.isArray($scope.fsGridConfig.tooltip) ? $scope.fsGridConfig.tooltip : [ $scope.fsGridConfig.tooltip ];
			}
			fsGridCtrl.tooltip = $scope.fsGridConfig.tooltip;
			$scope.fsGridConfig.tooltipClass = $scope.fsGridConfig.tooltipClass || defaultTooltipClass;
			$scope.fsGridConfig.tooltipPlacement = $scope.fsGridConfig.tooltipPlacement || defaultTooltipPlacement;
			$scope.fsGridConfig.tooltipTemplateUrl = $scope.fsGridConfig.tooltipTemplateUrl || defaultTooltipTemplateUrl;
		}
		function prepareDynamicParameter($scope, fsGridCtrl) {
			var dynamicParam, ngModelStr;
			if ($scope.fsGridConfig.dynamicParameter) {$scope.fsGridConfig.model
				ngModelStr = $scope.fsGridConfig.model.replace(/.value/, "");
				dynamicParam = $scope.$eval('ngModel.' + ngModelStr);
				if (dynamicParam) {
					angular.merge(dynamicParam, $scope.fsGridConfig.dynamicParameter);
				}
			}
		}
		function evalBooleanExpression(expression, $scope){
			if (expression === true || expression === false) {
				return expression;
			} else {
				return $parse(expression)($scope);
			}
		}
	}
	
	//<fs-color ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsColor.$inject = [ '$templateCache', '$compile', '$timeout' ];
	function fsColor($templateCache, $compile) {
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsColor'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content;
				initComponent(ctrls[1], ctrls[0]);
				content = $templateCache.get('fs/layout/template/fsColor.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}
	
	//<fs-label ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsLabel.$inject = [];
	function fsLabel() {
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsLabel'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				initComponent(ctrls[1], ctrls[0]);
			},
			templateUrl : 'fs/layout/template/fsLabel.html'
		};
	}
	
	//<fs-link ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsLink.$inject = ['$compile', '$templateCache'];
	function fsLink($compile, $templateCache) {
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsLink', '^^?form','^^?uiGrid'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, formCtrl = ctrls[2], uiGridCtrl = ctrls[3], filter;
				initComponent(ctrls[1], ctrls[0]);
				$scope.linkClick = function() {
					var callback;
					if (angular.isString($scope.fsGridConfig.options)) {
						$scope.fsGridConfig.options = ((uiGridCtrl && uiGridCtrl.grid) ? uiGridCtrl.grid.appScope : $scope).$eval($scope.fsGridConfig.options);
					}
					callback = $scope.fsGridConfig.options ? $scope.fsGridConfig.options.callback : $scope.fsGridConfig.callback;
					if (callback && angular.isString(callback.click)) {
						callback = ((uiGridCtrl && uiGridCtrl.grid) ? uiGridCtrl.grid.appScope : $scope.fsGridConfig.appScope).$eval(callback.click);
					}
					if (callback && angular.isFunction(callback)) {
						if (!$scope.ngModel && ($scope.uiGridRow)) {
							callback($scope.uiGridRow.entity, formCtrl);
						} else {
							callback($scope.ngModel, formCtrl);
						}
					}
				};
				content = $templateCache.get('fs/layout/template/fsLink.html');
				if ($scope.fsGridConfig.filter) {
					filter = $scope.fsGridConfig.filter;
				} else {
					filter = 'translate';
				}
				content = content.replace(/MODEL_NAME/g, '{{ ngModel.' + $scope.fsGridConfig.model + ' | ' + filter + ' }}');
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-image ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsImage.$inject = [ '$templateCache', '$compile', '$timeout' ];
	function fsImage($templateCache, $compile, $timeout) {
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsImage'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content;
				initComponent(ctrls[1], ctrls[0]);
				content = $templateCache.get('fs/layout/template/fsImage.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-text ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsText.$inject = [ '$templateCache', '$compile', '$timeout', '$parse' ];
	function fsText($templateCache, $compile, $timeout, $parse) {
		function TextPosition(textElement, ngModelCtrl) {
			var self = this;
			self.textElement = textElement;
			self.start = 0;
			self.end = 0;
			self.insert = function(ngModel, fsGridConfig, pattern) {
				var getter = $parse(fsGridConfig.model), value = getter(ngModel) || '';
				var pre = value.substr(0, self.start);
				var post = value.substr(self.end);
				value = pre + pattern + post;
				getter.assign(ngModel, value);
			};
			self.savePosition = function() {
				if (typeof (self.textElement.selectionStart) == "number") {
					ff();
				} else if (document.selection) {
					ie();
				}
			};
			init();
			function init() {
				self.textElement.onkeydown = self.savePosition;
				self.textElement.onkeyup = self.savePosition;
				self.textElement.onmousedown = self.savePosition;
				self.textElement.onmouseup = self.savePosition;
				self.textElement.onfocus = self.savePosition;
			}
			function ff() {
				self.start = self.textElement.selectionStart;
				self.end = self.textElement.selectionEnd;
			}
			function ie() {
				var range = document.selection.createRange();
				if (range.parentElement().id == self.textElement.id) {
					var range_all = document.body.createTextRange();
					range_all.moveToElementText(self.textElement);
					for (self.start = 0; range_all.compareEndPoints("StartToStart", range) < 0; self.start++) {
						range_all.moveStart('character', 1);
					}
					for (var i = 0; i <= this.start; i++) {
						if (self.textElement.value.charAt(i) == '\n')
							self.start++;
					}
					var range_all = document.body.createTextRange();
					range_all.moveToElementText(self.textElement);
					for (self.end = 0; range_all.compareEndPoints('StartToEnd', range) < 0; self.end++) {
						range_all.moveStart('character', 1);
					}
					for (var i = 0; i <= self.end; i++) {
						if (self.textElement.value.charAt(i) == '\n')
							self.end++;
					}
				}
			}
		}
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsText', '^^?uiGrid'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : [ '$scope', '$element', '$attrs', function($scope, $element, $attrs) {
				var ctrl = this;
				ctrl.isPatternActive = function() {
					if (!$scope.fsGridConfig.patternOptions) {
						return false;
					}
					if (!$scope.fsGridConfig.patternOptions.active && $scope.fsGridConfig.patternOptions.active != false) {
						return true;
					}
					return $parse($scope.fsGridConfig.patternOptions.active)($scope.ngModel) || $parse($scope.fsGridConfig.patternOptions.active)($scope)
				};
				ctrl.initTextPosition = function() {
					if (ctrl.isPatternActive()) {
						ctrl.textPosition = new TextPosition($element[0].querySelector('#textInput'));
					}
				};
				ctrl.insertPattern = function(pattern) {
					if (ctrl.textPosition) {
						ctrl.textPosition.insert($scope.ngModel, $scope.fsGridConfig, pattern);
					}
				};
			}],
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var watchers = [], watcher, content;
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function() {
					(watcher || angular.noop)();
				});
				$scope.isInTable = function() {
					return ctrls[2] ? true : false;
				};
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if($scope.fsGridConfig.options) {
					if (angular.isString($scope.fsGridConfig.options.data)) {
						watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener);
						watchers.push(watcher);
					}
				}
				if (angular.isString($scope.fsGridConfig.patternOptions)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.patternOptions, gridPatternOptionsListener);
					watchers.push(watcher);
				} else if($scope.fsGridConfig.patternOptions) {
					if (angular.isString($scope.fsGridConfig.patternOptions.data)) {
						watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.patternOptions.data), gridPatternOptionDataListener);
						watchers.push(watcher);
					}
				}
				function gridOptionsListener(options) {
					$scope.fsGridConfig.options = options;
				}
				function gridOptionDataListener(data) {
					$scope.fsGridConfig.options.data = data;
				}
				function gridPatternOptionsListener(patternOptions) {
					$scope.fsGridConfig.patternOptions = patternOptions;
				}
				function gridPatternOptionDataListener(data) {
					if($scope.fsGridConfig.patternOptions){
					    $scope.fsGridConfig.patternOptions.data = data;
					}
				}
				content = $templateCache.get('fs/layout/template/fsText.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-password ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsPassword.$inject = [ '$templateCache', '$compile', '$timeout' ];
	function fsPassword($templateCache, $compile, $timeout) {
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsPassword'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content;
				initComponent(ctrls[1], ctrls[0]);
				$scope.isVisible = 'invisible';
				$scope.clickVisible = function(){
					if($scope.isVisible == 'invisible'){
						$scope.isVisible = 'visible';
					}else{
						$scope.isVisible = 'invisible';
					}
				};
				content = $templateCache.get('fs/layout/template/fsPassword.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-checkbox ng-model="vm.company"/>
	fsCheckbox.$inject = [ '$templateCache', '$compile', '$parse', '$timeout' ];
	function fsCheckbox($templateCache, $compile, $parse, $timeout) {
		return {
			restrict : 'EA',
			require : ['^fsGrid', '^^?form', 'fsCheckbox'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : [ '$scope', '$element', '$attrs', '$transclude', function($scope, $element, $attrs, $transclude) {
				var getter = $parse($scope.fsGridConfig.model);
				$scope.indexOf = function(option) {
					var index = -1;
					if (!$scope.ngModel)
						return;
					angular.forEach(getter($scope.ngModel), function(value, key) {
						if (value == option) {
							index = key;
						}
					});
					return index;
				};
			} ],
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, watcher, watched, watchers = [], getter = $parse($scope.fsGridConfig.model), formCtrl = (ctrls.length == 2 ? ctrls[1] : undefined);
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if($scope.fsGridConfig.options) {
					if (angular.isString($scope.fsGridConfig.options.data)) {
						watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener);
						watchers.push(watcher);
					}
				}
				$scope.toggleSelection = function toggleSelection(option) {
					var list, getter = $parse($scope.fsGridConfig.model);
					if (!$scope.ngModel)
						return;
					if (formCtrl && $scope.fsGridConfig && formCtrl[$scope.fsGridConfig.model]) {
						formCtrl[$scope.fsGridConfig.model].$dirty = true;
					}
					list = getter($scope.ngModel) || [];
					var index = $scope.indexOf(option);
					if (index > -1) {
						list.splice(index, 1);
						if (list.length == 0) {
							getter.assign($scope.ngModel, null);
						} else {
							getter.assign($scope.ngModel, angular.copy(list));
						}
					} else {
						if($scope.fsGridConfig.options.maxChoiceNum && list.length == $scope.fsGridConfig.options.maxChoiceNum) {
							list.push(option);
							list.shift();
							getter.assign($scope.ngModel, angular.copy(list));
						} else {
							list.push(option);
							getter.assign($scope.ngModel, angular.copy(list));
						}
					}
				};
				$scope.clicked = function(){
					if ($scope.fsGridConfig.options.callback) {
						if (angular.isString($scope.fsGridConfig.options.callback.clicked)) {
							var callback = $scope.fsGridConfig.appScope.$eval($scope.fsGridConfig.options.callback.clicked);
							if (!callback) {
								log.debug("cannot found the callback function:" + $scope.fsGridConfig.options.callback.clicked);
							} else {
								callback($scope.ngModel);
							}
						} else if (angular.isFunction($scope.fsGridConfig.options.callback.clicked)) {
							$scope.fsGridConfig.options.callback.clicked($scope.ngModel);
						}
					}
				};
				function gridOptionsListener(options) {
					var watcher;
					$scope.fsGridConfig.options = options;
					if (!watched && $scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener);
						watchers.push(watcher);
					}
				}
				function gridOptionDataListener(optionData) {
					$parse('data').assign($scope.fsGridConfig.options, optionData);
				}
				content = $templateCache.get('fs/layout/template/fsCheckbox.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}
	
	//<fs-checkbox-group ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsCheckboxGroup.$inject = [ '$templateCache', '$compile', '$timeout' ];
	function fsCheckboxGroup($templateCache, $compile, $timeout) {
		return {
			restrict : 'EA',
			require : ['^fsGrid', '^^?form', 'fsCheckboxGroup'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : [ '$scope', '$element', '$attrs', '$transclude', '$parse', function($scope, $element, $attrs, $transclude, $parse) {
				var getter = $parse($scope.fsGridConfig.model);
				$scope.indexOfModel = function(option) {
					var i, model = getter($scope.ngModel) || [];
					for (i = 0; i < model.length; i++) {
						if (model[i] == option) {
							return i;
						}
					}
					return -1;
				};
				$scope.toggleSelectionAll = function($event, group, option) {
					var index, model;
					$event.stopPropagation();
					if (!$scope.ngModel)
						return;
					model = getter($scope.ngModel);
					if (!model) {
						getter.assign($scope.ngModel, []);
						model = getter($scope.ngModel);
					}
					if (option !== undefined) {
						index = $scope.indexOfModel(option);
						if (index > -1) {
							model.splice(index, 1);
							group.allChecked = false;
						} else {
							model.push(option);
							group.allChecked = allChecked(group.data);
						}
					} else {
						if (!group.allChecked) {
							angular.forEach(group.data, function(item) {
								if (model.length > 0) {
									index = $scope.indexOfModel(item.value);
									if (index > -1) {
										model.splice(index, 1);
									}
								}
							});
						} else {
							angular.forEach(group.data, function(item) {
								index = $scope.indexOfModel(item.value);
								if (index < 0) {
									model.push(item.value);
								}
							});
						}
					}
				};
				$scope.toggleGroupCollapse = function(group) {
					group.isCollapsed = !group.isCollapsed;
				};
				function allChecked(options) {
					var i, j, model;
					if (!$scope.ngModel)
						return false;
					model = getter($scope.ngModel) || [];
					options = options || [];
					if (model.length < options.length) {
						return false;
					}
					if ($scope.fsGridConfig.options.groups.length == 1 && model.length == options.length) {
						return true;
					}
					for (i = 0; i < options.length; i++) {
						for (j = 0; j < model.length; j++) {
							if (options[i].value == model[j]) {
								break;
							}
						}
						if (j >= model.length) {
							return false;
						}
					}
					return true;
				};
			} ],
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, watcher, watchers = [];
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if($scope.fsGridConfig.options) {
					angular.forEach($scope.fsGridConfig.options.groups, function(group){
						if (group && angular.isString(group.data)) {
							watcher = watchGridScope(ctrls[0], $parse(group.data), gridOptionDataListener);
							watchers.push(watcher);
						}
					});
				}
				function gridOptionsListener(options) {
					var watcher;
					if (options) {
						$scope.fsGridConfig.options = options;
						angular.forEach($scope.fsGridConfig.options.groups, function(group){
							if (group && !group.watched && angular.isString(group.data)) {
								group.watched = true;
								watcher = watchGridScope(ctrls[0], $parse(group.data), gridOptionDataListener);
								watchers.push(watcher);
							}
						});
					}
				}
				function gridOptionDataListener(groupData) {
					$parse('data').assign(group, groupData);
				}
				content = $templateCache.get('fs/layout/template/fsCheckboxGroup.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-date ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsDate.$inject = [ '$templateCache', '$compile', '$parse', '$timeout','commonService', '$q', '$injector' ];
	function fsDate($templateCache, $compile, $parse, $timeout, commonService, $q, $injector) {
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsDate'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controllerAs : 'formComponentCtrl',
			controller : [ '$scope', '$element', '$attrs', '$transclude', 'storageService', 
					function($scope, $element, $attrs, $transclude, storageService) {
						var self = this, formatInfo = storageService.getFormatInfo();
						$scope.fsGridConfig.placeholder = formatInfo[$scope.fsGridConfig.format] || $scope.fsGridConfig.format || formatInfo.defaultDate;
						self.altInputFormats = [ formatInfo[$scope.fsGridConfig.format] || $scope.fsGridConfig.format  || formatInfo.defaultDate ];
						$scope._opened = false;
						$scope._dateOptions = {
							datepickerMode : 'day',
							startingDay : 0,
							watchActiveDate: function(activeDate){
								$scope.activeDate = activeDate;
								var defer = $q.defer();
								var activeDateYear = $scope.activeDate ? $scope.activeDate.getFullYear() : new Date().getFullYear();
								var modelDateYear = $parse($scope.fsGridConfig.model)($scope.ngModel) ? $parse($scope.fsGridConfig.model)($scope.ngModel).getFullYear() : new Date().getFullYear();
								var calendarYear = (activeDateYear || modelDateYear);
								if (!calendarYear) {
									defer.reject();
									return defer.promise;
								}
								var params = {
									calendarYear : calendarYear,
									calendarCodes : $scope._dateOptions.calendarCodes
								};
								if(($scope.holidays && $scope.holidays[params.calendarYear]) || !params.calendarCodes) {
									defer.reject();
									return defer.promise;
								}
								commonService.getHolidays(params).then(function(days) {
									if(!$scope.holidays) $scope.holidays = {};
									angular.forEach(params.calendarCodes, function(calendarCode) {
										if(!$scope.holidays[params.calendarYear]) $scope.holidays[params.calendarYear] = {};
										$scope.holidays[params.calendarYear][calendarCode] = days;
									});
									defer.resolve();
								}, function(e) {
									log.debug(e);
								});
								return defer.promise;
							},
							getBizDate : function() {
								return storageService.getSiteInfo() ? storageService.getSiteInfo().bizDate : new Date();
							}
						};
						if(!($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.initDate)) {
							$scope._dateOptions.initDate = $scope._dateOptions.getBizDate();
						}
					} ],
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, watcher, watched, watchers = [], fsGridCtrl = ctrls[0];
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options) {
					if (angular.isString($scope.fsGridConfig.options.startingDay)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.startingDay, gridOptionStartingDayListener);
						watchers.push(watcher);
					}
					if (angular.isString($scope.fsGridConfig.options.dateDisabled)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.dateDisabled, gridOptionDateDisabledListener);
						watchers.push(watcher);
					} else {
						gridOptionDateDisabledListener($scope.fsGridConfig.options.dateDisabled);
					}
					if (angular.isString($scope.fsGridConfig.options.calendarCodes)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.calendarCodes, gridOptionCalendarCodesListener);
						watchers.push(watcher);
					} else {
						gridOptionCalendarCodesListener($scope.fsGridConfig.options.calendarCodes);
					}
					if (angular.isString($scope.fsGridConfig.options.maxDate)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.maxDate, gridOptionMaxDateListener);
						watchers.push(watcher);
					}
					if (angular.isString($scope.fsGridConfig.options.minDate)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.minDate, gridOptionMinDateListener);
						watchers.push(watcher);
					}
					if (angular.isString($scope.fsGridConfig.options.initDate)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.initDate, gridOptionInitDateListener);
						watchers.push(watcher);
					}
				} else if (!$scope.fsGridConfig.options) {
					$scope.fsGridConfig.options = {};
				}
				watcher = $scope.$watch($parse('ngModel.'+ $scope.fsGridConfig.model), function(date) {
					if (angular.isDate(date)) {
						date.setHours(0);
						date.setMinutes(0);
						date.setSeconds(0);
						date.setMilliseconds(0);
					}
				});
				watchers.push(watcher);
				watcher = $scope.$watch(function(){
					var activeDateYear = $scope.activeDate ? $scope.activeDate.getFullYear() : new Date().getFullYear();
					var modelDateYear = $parse($scope.fsGridConfig.model)($scope.ngModel) ? $parse($scope.fsGridConfig.model)($scope.ngModel).getFullYear() : new Date().getFullYear();
					var calendarYear = (modelDateYear || activeDateYear);
					if (!calendarYear)
						return;
					return {
						calendarYear : calendarYear,
						calendarCodes : $scope._dateOptions.calendarCodes
					};
				}, function(params, oldParams) {
					if ($scope.fsGridConfig.options && !$scope.fsGridConfig.options.dateDisabled && params && params.calendarCodes && params.calendarCodes.length > 0 && (!$scope.holidays || !angular.equals(params, oldParams))) {
						if($scope.holidays && $scope.holidays[params.calendarYear]) {
							return;
						}
						commonService.getHolidays(params).then(function(days) {
							if(!$scope.holidays) $scope.holidays = {};
							angular.forEach(params.calendarCodes, function(calendarCode) {
								if(!$scope.holidays[params.calendarYear]) $scope.holidays[params.calendarYear] = {};
								$scope.holidays[params.calendarYear][calendarCode] = days;
							});
							//holidaycheck
							var gridConfig = $scope.fsGridConfig;
							if(gridConfig.rules){
								angular.forEach(gridConfig.rules.ruleInfos, function(rule) {
									var ruleName = angular.lowercase(rule.ruleName);
									if ("holidaycheck" == ruleName) {
										var d = $q.defer(), validateResult = {
											'field' : gridConfig.model,
											'rule' : rule.ruleName,
											'messages' : null
										};
										rule.fieldLabel = gridConfig.label;
										rule.ngModel = ctrls[0].getGridEntity();
										rule.operatedField = gridConfig.field || gridConfig.model;
										rule.modelType = gridConfig.type;
										rule.$scope = $scope;
										$injector.get(ruleName).validate($parse(gridConfig.model)(rule.ngModel), undefined, rule).then(function(e) {
											validateResult.invalid = false;
											fsGridCtrl.removeValidateMessage(validateResult);
											rule.fieldCtrl.$setValidity(rule.ruleName, true);
											d.resolve();
										}, function(e) {
											validateResult.invalid = true;
											validateResult.messages = e.error;
											fsGridCtrl.saveValidateMessage(validateResult);
											rule.fieldCtrl.$setValidity(rule.ruleName, false);
											d.reject();
										});
									}
								});
							}
						}, function(e) {
							log.debug(e);
							$scope.holidays = {};
						});
					}
				}, true);
				watchers.push(watcher);
				function gridOptionsListener(options) {
					var watcher;
					$scope.fsGridConfig.options = options;
					if(!watched && $scope.fsGridConfig.options) {
						watched = true;
						if (angular.isString($scope.fsGridConfig.options.startingDay)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.startingDay, gridOptionStartingDayListener);
							watchers.push(watcher);
						}
						if (angular.isString($scope.fsGridConfig.options.dateDisabled)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.dateDisabled, gridOptionDateDisabledListener);
							watchers.push(watcher);
						} else {
							gridOptionDateDisabledListener($scope.fsGridConfig.options.dateDisabled);
						}
						if (angular.isString($scope.fsGridConfig.options.calendarCodes)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.calendarCodes, gridOptionCalendarCodesListener);
							watchers.push(watcher);
						} else {
							gridOptionCalendarCodesListener($scope.fsGridConfig.options.calendarCodes);
						}
						if (angular.isString($scope.fsGridConfig.options.maxDate)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.maxDate, gridOptionMaxDateListener);
							watchers.push(watcher);
						}
						if (angular.isString($scope.fsGridConfig.options.minDate)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.minDate, gridOptionMinDateListener);
							watchers.push(watcher);
						}
						if (angular.isString($scope.fsGridConfig.options.initDate)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.initDate, gridOptionInitDateListener);
							watchers.push(watcher);
						}
					}
				}
				function gridOptionStartingDayListener(startingDay) {
					$scope._dateOptions.startingDay = startingDay || 0;
				}
				function gridOptionDateDisabledListener(disableFunction) {
					$scope._dateOptions.dateDisabled = disableFunction;
				}
				function gridOptionCalendarCodesListener(calendarCodes) {
					$scope._dateOptions.calendarCodes = calendarCodes;
					if (!$scope._dateOptions.dateDisabled) {
						$scope._dateOptions.dateDisabled = function(dateModel) {
							if (dateModel.mode === 'day') {
								var holidaysArray = [], year = dateModel.date.getFullYear();
								if(calendarCodes && $scope.holidays && $scope.holidays[year]) {
									angular.forEach(calendarCodes, function(calendarCode) {
										holidaysArray = holidaysArray.concat($scope.holidays[year][calendarCode]);
									});
								}
								return commonService.isHoliday(dateModel.date, holidaysArray);
							} else {
								return false;
							}
						};
					}
				}
				function gridOptionMaxDateListener(maxDate) {
					$scope._dateOptions.maxDate = maxDate;
				}
				function gridOptionMinDateListener(minDate) {
					$scope._dateOptions.minDate = minDate;
				}
				function gridOptionInitDateListener(initDate) {
					$scope._dateOptions.initDate = initDate;
				}
				content = $templateCache.get('fs/layout/template/fsDate.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}
	
	//<fs-time ng-model="vm.company.cuttoffTime" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsTime.$inject = [ '$templateCache', '$compile', '$parse', '$timeout' ];
	function fsTime($templateCache, $compile, $parse, $timeout) {
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsTime'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, watcher, watchers = [];
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if (!$scope.fsGridConfig.options) {
					$scope.fsGridConfig.options = { 'showSeconds' : true };
				} else if ($scope.fsGridConfig.options.showSeconds == undefined) {
					$scope.fsGridConfig.options.showSeconds = true;
				}
				watcher = $scope.$watch($parse('ngModel.' + $scope.fsGridConfig.model), function(date) {
					if (angular.isDate(date)) {
						date.setYear(1970);
						date.setMonth(0);
						date.setDate(1);
						date.setSeconds(0);
						date.setMilliseconds(0);
					}
				});
				watchers.push(watcher);
				function gridOptionsListener(options) {
					$scope.fsGridConfig.options = options;
					if($scope.fsGridConfig.options && $scope.fsGridConfig.options.showSeconds == undefined) {
						$scope.fsGridConfig.options.showSeconds = true;
					}
				}
				content = $templateCache.get('fs/layout/template/fsTime.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};		
	}
	
	//<fs-datetime ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsDatetime.$inject = [ '$templateCache', '$compile', '$parse', '$timeout', 'commonService', '$q', '$injector' ];
	function fsDatetime($templateCache, $compile, $parse, $timeout, commonService, $q, $injector) {
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsDatetime'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : [ '$scope', '$element', '$attrs', '$transclude', '$parse', 'storageService', 
					function($scope, $element, $attrs, $transclude, $parse, storageService) {
						var self = this;
						var formatInfo = storageService.getFormatInfo();
						$scope.fsGridConfig.placeholder = formatInfo[$scope.fsGridConfig.format] || $scope.fsGridConfig.format || formatInfo.defaultDate;
						self.altInputFormats = [ formatInfo[$scope.fsGridConfig.format] || $scope.fsGridConfig.format  || formatInfo.defaultDate ];
						$scope._dateOptions = {
							datepickerMode : 'day',
							startingDay : 0,
							watchActiveDate: function(activeDate){
								$scope.activeDate = activeDate;
								var defer = $q.defer();
								var activeDateYear = $scope.activeDate ? $scope.activeDate.getFullYear() : new Date().getFullYear();
								var modelDateYear = $parse($scope.fsGridConfig.model)($scope.ngModel) ? $parse($scope.fsGridConfig.model)($scope.ngModel).getFullYear() : new Date().getFullYear();
								var calendarYear = (activeDateYear || modelDateYear);
								if (!calendarYear) {
									defer.reject();
									return defer.promise;
								}
								var params = {
									calendarYear : calendarYear,
									calendarCodes : $scope._dateOptions.calendarCodes
								};
								if(($scope.holidays && $scope.holidays[params.calendarYear]) || !params.calendarCodes) {
									defer.reject();
									return defer.promise;
								}
								commonService.getHolidays(params).then(function(days) {
									if(!$scope.holidays) $scope.holidays = {};
									angular.forEach(params.calendarCodes, function(calendarCode) {
										if(!$scope.holidays[params.calendarYear]) $scope.holidays[params.calendarYear] = {};
										$scope.holidays[params.calendarYear][calendarCode] = days;
									});
									defer.resolve();
								}, function(e) {
									log.debug(e);
								});
								return defer.promise;
							},
							getBizDate : function() {
								return storageService.getSiteInfo() ? storageService.getSiteInfo().bizDate : new Date();
							}
						};
						if(!($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.initDate)) {
							$scope._dateOptions.initDate = $scope._dateOptions.getBizDate();
						}
						
			} ],
			controllerAs : 'formComponentCtrl',
			link : function ($scope, $element, $attrs, ctrls, $transclude) {
				var content, watcher, watched, watchers = [], fsGridCtrl = ctrls[0];
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = $scope.$watch($scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if (!$scope.fsGridConfig.options) {
					$scope.fsGridConfig.options = { 'showSeconds' : true };
				} else if ($scope.fsGridConfig.options) {
					$scope.fsGridConfig.options.showSeconds = $scope.fsGridConfig.options.showSeconds == undefined ? true : $scope.fsGridConfig.options.showSeconds;
					if (angular.isString($scope.fsGridConfig.options.startingDay)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.startingDay, gridOptionStartingDayListener);
						watchers.push(watcher);
					}
					if (angular.isString($scope.fsGridConfig.options.dateDisabled)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.dateDisabled, gridOptionDateDisabledListener);
						watchers.push(watcher);
					} else {
						gridOptionDateDisabledListener($scope.fsGridConfig.options.dateDisabled);
					}
					if (angular.isString($scope.fsGridConfig.options.calendarCodes)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.calendarCodes, gridOptionCalendarCodesListener);
						watchers.push(watcher);
					} else {
						gridOptionCalendarCodesListener($scope.fsGridConfig.options.calendarCodes);
					}
					if (angular.isString($scope.fsGridConfig.options.maxDate)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.maxDate, gridOptionMaxDateListener);
						watchers.push(watcher);
					}
					if (angular.isString($scope.fsGridConfig.options.minDate)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.minDate, gridOptionMinDateListener);
						watchers.push(watcher);
					}
					if (angular.isString($scope.fsGridConfig.options.initDate)) {
						watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.initDate, gridOptionInitDateListener);
						watchers.push(watcher);
					}
				} else if (!$scope.fsGridConfig.options) {
					$scope.fsGridConfig.options = {};
				}
				watcher = $scope.$watch($parse('ngModel.' + $scope.fsGridConfig.model), function(date) {
					if (angular.isDate(date)) {
						date.setMilliseconds(0);
					}
				});
				watchers.push(watcher);
				watcher = $scope.$watch(function(){
					var activeDateYear = $scope.activeDate ? $scope.activeDate.getFullYear() : new Date().getFullYear();
					var modelDateYear = $parse($scope.fsGridConfig.model)($scope.ngModel) ? $parse($scope.fsGridConfig.model)($scope.ngModel).getFullYear() : new Date().getFullYear();
					var calendarYear = (modelDateYear || activeDateYear);
					if (!calendarYear)
						return;
					return {
						calendarYear : calendarYear,
						calendarCodes : $scope._dateOptions.calendarCodes
					};
				}, function(params, oldParams) {
					if ($scope.fsGridConfig.options && !$scope.fsGridConfig.options.dateDisabled && params && params.calendarCodes && params.calendarCodes.length > 0 && (!$scope.holidays || !angular.equals(params, oldParams))) {
						if($scope.holidays && $scope.holidays[params.calendarYear]) {
							return;
						}
						commonService.getHolidays(params).then(function(days) {
							if(!$scope.holidays) $scope.holidays = {};
							angular.forEach(params.calendarCodes, function(calendarCode) {
								if(!$scope.holidays[params.calendarYear]) $scope.holidays[params.calendarYear] = {};
								$scope.holidays[params.calendarYear][calendarCode] = days;
							});
							//holidaycheck
							var gridConfig = $scope.fsGridConfig;
							if(gridConfig.rules){
								angular.forEach(gridConfig.rules.ruleInfos, function(rule) {
									var ruleName = angular.lowercase(rule.ruleName);
									if ("holidaycheck" == ruleName) {
										var d = $q.defer(), validateResult = {
											'field' : gridConfig.model,
											'rule' : rule.ruleName,
											'messages' : null
										};
										rule.fieldLabel = gridConfig.label;
										rule.ngModel = ctrls[0].getGridEntity();
										rule.operatedField = gridConfig.field || gridConfig.model;
										rule.modelType = gridConfig.type;
										rule.$scope = $scope;
										$injector.get(ruleName).validate($parse(gridConfig.model)(rule.ngModel), undefined, rule).then(function(e) {
											validateResult.invalid = false;
											fsGridCtrl.removeValidateMessage(validateResult);
											rule.fieldCtrl.$setValidity(rule.ruleName, true);
											d.resolve();
										}, function(e) {
											validateResult.invalid = true;
											validateResult.messages = e.error;
											fsGridCtrl.saveValidateMessage(validateResult);
											rule.fieldCtrl.$setValidity(rule.ruleName, false);
											d.reject();
										});
									}
								});
							}
						}, function(e) {
							log.debug(e);
							$scope.holidays = {};
						});
					}
				}, true);
				watchers.push(watcher);
				function gridOptionsListener(options) {
					if(options == $scope.fsGridConfig.options) return;
					var watcher;
					$scope.fsGridConfig.options = options || { 'showSeconds' : true };
					if ($scope.fsGridConfig.options.showSeconds == undefined) {
						$scope.fsGridConfig.options.showSeconds = true;
					}
					if(!watched) {
						watched = true;
						if (angular.isString($scope.fsGridConfig.options.startingDay)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.startingDay, gridOptionStartingDayListener);
							watchers.push(watcher);
						}
						if (angular.isString($scope.fsGridConfig.options.dateDisabled)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.dateDisabled, gridOptionDateDisabledListener);
							watchers.push(watcher);
						} else {
							gridOptionDateDisabledListener($scope.fsGridConfig.options.dateDisabled);
						}
						if (angular.isString($scope.fsGridConfig.options.calendarCodes)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.calendarCodes, gridOptionCalendarCodesListener);
							watchers.push(watcher);
						} else {
							gridOptionCalendarCodesListener($scope.fsGridConfig.options.calendarCodes);
						}
						if (angular.isString($scope.fsGridConfig.options.maxDate)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.maxDate, gridOptionMaxDateListener);
							watchers.push(watcher);
						}
						if (angular.isString($scope.fsGridConfig.options.minDate)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.minDate, gridOptionMinDateListener);
							watchers.push(watcher);
						}
						if (angular.isString($scope.fsGridConfig.options.initDate)) {
							watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.initDate, gridOptionInitDateListener);
							watchers.push(watcher);
						}
					}
				}
				function gridOptionStartingDayListener(startingDay) {
					$scope._dateOptions.startingDay = startingDay || 0;
				}
				function gridOptionDateDisabledListener(disableFunction) {
					$scope._dateOptions.dateDisabled = disableFunction;
				}
				function gridOptionCalendarCodesListener(calendarCodes) {
					$scope._dateOptions.calendarCodes = calendarCodes;
					if (!$scope._dateOptions.dateDisabled) {
						$scope._dateOptions.dateDisabled = function(dateModel) {
							if (dateModel.mode === 'day') {
								var holidaysArray = [], year = dateModel.date.getFullYear();
								if(calendarCodes && $scope.holidays && $scope.holidays[year]) {
									angular.forEach(calendarCodes, function(calendarCode) {
										holidaysArray = holidaysArray.concat($scope.holidays[year][calendarCode]);
									});
								}
								return commonService.isHoliday(dateModel.date, holidaysArray);
							} else {
								return false;
							}
						};
					}
				}
				function gridOptionMaxDateListener(maxDate) {
					$scope._dateOptions.maxDate = maxDate;
				}
				function gridOptionMinDateListener(minDate) {
					$scope._dateOptions.minDate = minDate;
				}
				function gridOptionInitDateListener(initDate) {
					$scope._dateOptions.initDate = initDate;
				}
				content = $templateCache.get('fs/layout/template/fsDatetime.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-calendar-day ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsCalendarDay.$inject = [ '$templateCache', '$compile', '$parse', '$timeout' ];
	function fsCalendarDay($templateCache, $compile, $parse, $timeout) {
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsCalendarDay'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, i, holidayGetter, holidayList, watcher, watchers=[];
				$scope._calendarOptions = [];
				holidayGetter = $parse('ngModel.' + $scope.fsGridConfig.model);
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options.calendarYear)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options.calendarYear, function(calendarYear) {
						buildCalendarOptions(calendarYear);
					});
					watchers.push(watcher);
				} else {
					buildCalendarOptions($scope.fsGridConfig.options.calendarYear);
				}
				function buildCalendarOptions(calendarYear){
					if (!calendarYear || !angular.isNumber(calendarYear)) {
						return;
					}
					for (i = 0; i< 12; i++) {
						$scope._calendarOptions[i] = {
							datepickerMode : 'day',
							startingDay : 0,
							showWeeks : false,
							initDate : new Date(calendarYear, i, 1, 0, 0, 0, 0),
							customClass : customClass,
							toggleHoliday : toggleHoliday,
							readonly : ctrls[1].isReadonly($scope.fsGridConfig)
						};
					}
				}
				function customClass(dt) {
					var holiday;
					dt.isHoliday = false;
					holidayList = holidayGetter($scope) || [];
					for (i = 0; i< holidayList.length; i++) {
						holiday = holidayList[i];
						if (holiday && angular.isDate(holiday) && holiday.getYear() == dt.date.getYear() && holiday.getMonth() == dt.date.getMonth()
								&& holiday.getDate() == dt.date.getDate()) {
							dt.isHoliday = true;
							return "btn-holiday";
						}
					}
					return "";
				}
				function toggleHoliday(dt) {
					var holiday;
					if ($scope.view) {
						return;
					}
					holidayList = holidayGetter($scope) || [];
					if (!dt.isHoliday) {
						holidayList.push(dt.date);
					} else {
						for (i = 0; i< holidayList.length; i++) {
							holiday = holidayList[i];
							if (holiday && holiday.getYear() == dt.date.getYear() && holiday.getMonth() == dt.date.getMonth()
									&& holiday.getDate() == dt.date.getDate()) {
								holidayList.splice(i, 1);
								break;
							}
						}
					}
					holidayGetter.assign($scope, holidayList);
				}
				content = $templateCache.get('fs/layout/template/fsCalendarDay.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = content.replace(/CALENDAR_TEMPLATE_URL/g, 'waf/view/'+ctrls[0].getGridAppScope().app.settings.theme+'/template/calendar/calendarDay.html');
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
		
	}

	//<fs-radio ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsRadio.$inject = [ '$templateCache', '$compile', '$timeout', '$parse' ];
	function fsRadio($templateCache, $compile, $timeout, $parse) {
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsRadio'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, watcher, watched, watchers=[];
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if($scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
					watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener);
					watchers.push(watcher);
				}
				function gridOptionsListener(options) {
					var watcher;
					$scope.fsGridConfig.options = options;
					if (!watched && $scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener);
						watchers.push(watcher);
					}
				}
				function gridOptionDataListener(data) {
					$parse('data').assign($scope.fsGridConfig.options, data);
				}
				content = $templateCache.get('fs/layout/template/fsRadio.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-textarea ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsTextarea.$inject = [ '$templateCache', '$compile', '$timeout' ];
	function fsTextarea($templateCache, $compile, $timeout) {
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsTextarea'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, watcher, watchers=[];
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} 
				function gridOptionsListener(options) {
					$scope.fsGridConfig.options = options;
				}
				content = $templateCache.get('fs/layout/template/fsTextarea.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-select ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsSelect.$inject = [ '$templateCache', '$compile', '$parse', '$timeout' ];
	function fsSelect($templateCache, $compile, $parse, $timeout) {
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsSelect'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, watcher, watched, watchers=[];
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if($scope.fsGridConfig.options) {
					$scope.fsGridConfig.options.hasEmptyOption = ($scope.fsGridConfig.options.hasEmptyOption == undefined) ? true : $scope.fsGridConfig.options.hasEmptyOption;
					if (angular.isString($scope.fsGridConfig.options.data)) {
						watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener);
						watchers.push(watcher);
					}
				}
				watchers.push(watcher);
				function gridOptionsListener(options) {
					$scope.fsGridConfig.options = options;
					if($scope.fsGridConfig.options) $scope.fsGridConfig.options.hasEmptyOption = ($scope.fsGridConfig.options.hasEmptyOption == undefined) ? true : $scope.fsGridConfig.options.hasEmptyOption;
					if (!watched && $scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						watchers.push(watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener));
					}
				}
				function gridOptionDataListener(data) {
					if(data && data.length > 0) {
						if(data[0].value == '' || data[0].value == null) {
							data = data.slice(1);
							console.log("model name " + $scope.fsGridConfig.model  +" of dropdown must not have empty option in the option data, please modify the code.");
						}
					}
					$parse('data').assign($scope.fsGridConfig.options, data);
				}
				content = $templateCache.get('fs/layout/template/fsSelect.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-multiple-select ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsMultipleSelect.$inject = [ '$templateCache', '$compile', '$parse', '$timeout' ];
	function fsMultipleSelect($templateCache, $compile, $parse, $timeout) {
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsMultipleSelect'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, watcher, watched, watchers=[];
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $scope.fsGridConfig.options, gridOptionsListener);
					watchers.push(watcher);
				} else if($scope.fsGridConfig.options) {
					if (angular.isString($scope.fsGridConfig.options.data)) {
						watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener);
						watchers.push(watcher);
					}
				}
				function gridOptionsListener(options) {
					$scope.fsGridConfig.options = options;
					if (!watched && $scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						watchers.push(watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener));
					}
				}
				function gridOptionDataListener(data) {
					$parse('data').assign($scope.fsGridConfig.options, data);
				}
				content = $templateCache.get('fs/layout/template/fsMultipleSelect.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}
	
	//<fs-file-upload ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsFileUpload.$inject = [ '$templateCache', '$compile', '$parse', '$timeout', 'FileUploader', 'modalService', 'commonService' ];
	function fsFileUpload($templateCache, $compile, $parse, $timeout , FileUploader, modalService, commonService) {
		var defaultMimeTypes = [ 
             {
           	  'label' : 'Word',
           	  'value' : 'application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document'
             }, {
           	  'label' : 'Excel',
           	  'value' : 'application/vnd.ms-excel,application/x-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
             }, {
           	  'label' : 'PDF',
           	  'value' : 'application/pdf'
             }, {
           	  'label' : 'Image',
           	  'value' : 'image/*'
             }, {
           	  'label' : 'XML',
           	  'value' : 'text/xml'
             }, {
           	  'label' : 'Text',
           	  'value' : 'text/*'
             } ];
		var defaultFileUploadOptions = {
			url : 'do/cda/file/upload',
			downloadUrl : 'do/cda/file/download',
			alias : 'file',
			//headers {Object}: Headers to be sent along with the files. HTML5 browsers only.
			//formData {Array}: Data to be sent along with the files
			autoUpload: true,
			method: "POST",
			removeAfterUpload: false,
			isMultiple : false,
			mimeTypes : "",
			withCredentials: true
		};
		return {
			restrict : 'EA',
			require : ['^fsGrid', 'fsFileUpload'],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, watcher, watched, watchers=[];
				var log = ctrls[0].getGridAppScope().loggerFactory.getLogger('fsFileUpload');
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options), gridOptionListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
					watchers.push(watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener));
				} else if (!$scope.fsGridConfig.options) {
					$scope.fsGridConfig.options = { 'data' : defaultMimeTypes, 'hiddenMimeOptions' : false };
				} else if (!$scope.fsGridConfig.options.data) {
					$scope.fsGridConfig.options.data = defaultMimeTypes;
				}
				$scope._remove = function(fileItem) {
					angular.forEach($scope.ngModel[$scope.fsGridConfig.model], function(file, index) {
						if (fileItem.docId == file.docId) {
							$scope.ngModel[$scope.fsGridConfig.model].splice(index, 1);
						}
					});
				};
				$scope._download = function($event, fileItem) {
					var btn = ($event.srcElement || $event.target).parentElement, downloadUrl = $scope._fileUploadOptions.downloadUrl;
					if (btn.disabled) {
						return false;
					}
					btn.disabled = true;
					$timeout(function() {
						btn.disabled = false;
					}, 1000);
					commonService.download(fileItem, downloadUrl);
				};
				$scope._open = function($event, fileItem) {
					var i, files, btn = ($event.srcElement || $event.target).parentElement, downloadUrl = $scope._fileUploadOptions.downloadUrl;
					if (btn.disabled) {
						return false;
					}
					btn.disabled = true;
					if (fileItem.hasOwnProperty('$$hashKey')) {
						files = $scope.ngModel[$scope.fsGridConfig.model];
						for (i = 0; i < files.length; i++) {
							if (fileItem.docId == files[i].docId) {
								fileItem = files[i];
								break;
							}
						}
					}
					commonService.openDocument(fileItem, downloadUrl).then(function() {
						btn.disabled = false;
					}, function() {
						btn.disabled = false;
					});
				};
				buildFileUploader();
				function translateScalars(str) {
					if (angular.isString(str)) {
						if (str.search(/kb/i) === str.length - 2) {
							return parseFloat(str.substring(0, str.length - 2) * 1024);
						} else if (str.search(/mb/i) === str.length - 2) {
							return parseFloat(str.substring(0, str.length - 2) * 1048576);
						} else if (str.search(/gb/i) === str.length - 2) {
							return parseFloat(str.substring(0, str.length - 2) * 1073741824);
						} else if (str.search(/b/i) === str.length - 1) {
							return parseFloat(str.substring(0, str.length - 1));
						} else {
							return parseFloat(str);
						}
					}
					return str;
				}
				function buildFileUploader() {
					$scope._fileUploadOptions = angular.extend(defaultFileUploadOptions);
					if ($scope.fsGridConfig.options && !isNaN($scope.fsGridConfig.options.fileCount)) {
						$scope._fileUploadOptions.queueLimit = $scope.fsGridConfig.options.fileCount;
					}
					$scope._fileUploadOptions.url = ($scope.fsGridConfig.options && $scope.fsGridConfig.options.url) ? $scope.fsGridConfig.options.url : $scope._fileUploadOptions.url;
					$scope._fileUploadOptions.downloadUrl = ($scope.fsGridConfig.options && $scope.fsGridConfig.options.downloadUrl) ? $scope.fsGridConfig.options.downloadUrl : $scope._fileUploadOptions.downloadUrl;
					$scope._fileUploadOptions.isMultiple = ($scope.fsGridConfig.options && $scope.fsGridConfig.options.isMultiple) ? $scope.fsGridConfig.options.isMultiple : $scope._fileUploadOptions.isMultiple;
					$scope._fileUploadOptions.mimeTypes = ($scope.fsGridConfig.options && $scope.fsGridConfig.options.mimeTypes) ? $scope.fsGridConfig.options.mimeTypes : $scope._fileUploadOptions.mimeTypes;
					$scope._fileUploadOptions.maxFileSize = translateScalars(($scope.fsGridConfig.options && $scope.fsGridConfig.options.maxFileSize) ?  $scope.fsGridConfig.options.maxFileSize : $scope._fileUploadOptions.maxFileSize);
					$scope._fileUploadOptions.minFileSize = translateScalars(($scope.fsGridConfig.options && $scope.fsGridConfig.options.minFileSize) ?  $scope.fsGridConfig.options.minFileSize : ($scope._fileUploadOptions.minFileSize || '1b'));
					$scope._uploader = new FileUploader($scope._fileUploadOptions);
					$scope._uploader.filters.push({
						'name' : 'fileSize',
						'fn' : function(fileItem, options) {
							if (angular.isNumber(options.minFileSize) && angular.isNumber(options.maxFileSize)) {
								return fileItem.size >= options.minFileSize && fileItem.size <= options.maxFileSize;
							} else if (angular.isNumber(options.minFileSize)) {
								return fileItem.size >= options.minFileSize;
							} else if (angular.isNumber(options.maxFileSize)) {
								return fileItem.size <= options.maxFileSize;
							}
							return true;
						}
					});
					$scope._uploader.filters.push({
						'name' : 'customerQueueLimit',
						'fn' : function(fileItem, options) {
							return !$scope.ngModel[$scope.fsGridConfig.model] || $scope.ngModel[$scope.fsGridConfig.model].length < $scope._fileUploadOptions.queueLimit;
						}
					});
					$scope._uploader.onAfterAddingFile = function(){
						var fileSelect = $element[0].querySelector('#fileSelect');
						fileSelect.value = null;
					};
					$scope._uploader.onSuccessItem = onSuccessItem;
					$scope._uploader.onErrorItem = onErrorItem;
					$scope._uploader.onWhenAddingFileFailed = onWhenAddingFileFailed;
				}
				function onSuccessItem(fileItem, response, status, headers) {
					log.debug('file upload success:', fileItem, response, status, headers);
					if(fileItem.isSuccess){
						$scope.ngModel[$scope.fsGridConfig.model] = $scope.ngModel[$scope.fsGridConfig.model] || [];
						$scope.ngModel[$scope.fsGridConfig.model].push(response.data);
						fileItem.docId = response.data.docId;
						fileItem.remove();
					}
				}
				function onErrorItem(fileItem, response, status, headers) {
					log.debug('file upload error', fileItem, status, headers);
				}
				function onWhenAddingFileFailed(fileItem, filter, options) {
					if (filter.name == 'fileSize') {
						if(fileItem.size == 0) {
							modalService.openErrorDialogs({
								'messages' : [ 'label.global.file.empty' ]
							});
						} else if(fileItem.size > options.maxFileSize) {
							modalService.openErrorDialogs({
								'messages' : [ {msg : 'label.global.file.maxfilesize', args : [options.maxFileSize]}]
							
							});
						} else if (fileItem.size < options.minFileSize) {
							modalService.openErrorDialogs({
								'messages' : [ {msg : 'label.global.file.minfilesize', args : [options.minFileSize]}]
							});
						}
					} else if (filter.name == 'customerQueueLimit') {
						modalService.openErrorDialogs({
							'messages' : [ {msg : 'label.global.file.maxfilesize', args : [options.queueLimit]}]
						});
					}
					log.debug('file upload error', fileItem, filter, options);
				}
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options;
					if (!watched && $scope.fsGridConfig.options && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						watchers.push(watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener));
					} else if (!$scope.fsGridConfig.options.data){
						$scope.fsGridConfig.options.data = defaultMimeTypes;
					}
					if ($scope.fsGridConfig.options) {
						buildFileUploader();
					}
				}
				function gridOptionDataListener(data) {
					$parse('data').assign($scope.fsGridConfig.options, data);
				}
				content = $templateCache.get('fs/layout/template/fsFileUpload.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-typeahead ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsTypeahead.$inject = [ '$templateCache', '$compile', '$parse' ];
	function fsTypeahead($templateCache, $compile, $parse) {
		return {
			restrict : 'EA',
			require : [ '^fsGrid', '^^?form', 'fsTypeahead' ],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, watcher, watched, watchers=[];
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options), gridOptionListener);
					watchers.push(watcher);
				} else if ($scope.fsGridConfig.options) {
					if (angular.isString($scope.fsGridConfig.options.data)) {
						watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener);
						watchers.push(watcher);
					}
				}
				watcher = $scope.$watch($parse('ngModel.' +$scope.fsGridConfig.model), function(newValue) {
					setTypeaheadModel(newValue);
				});
				watchers.push(watcher);
				watcher = $scope.$watch($parse('fsGridConfig._typeaheadModel'), function(newValue) {
					setGridModel(newValue);
				});
				watchers.push(watcher);
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options || {};
					if (!watched && angular.isString($scope.fsGridConfig.options.data)) {
						watched = true;
						watchers.push(watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options.data), gridOptionDataListener));
					}
				}
				function gridOptionDataListener(data) {
					$parse('data').assign($scope.fsGridConfig.options, data);
					setTypeaheadModel();
				}
				function setGridModel(labelValue) {
					var i;
					if (!$scope.fsGridConfig.options || !$scope.fsGridConfig.options.data) {
						$parse($scope.fsGridConfig.model).assign($scope.ngModel, labelValue);
						return;
					}
					for (i = 0; i < $scope.fsGridConfig.options.data.length; i++) {
						if ($scope.fsGridConfig.options.data[i].label.toLowerCase() == labelValue.toLowerCase()) {
							$parse($scope.fsGridConfig.model).assign($scope.ngModel, $scope.fsGridConfig.options.data[i].value);
							break;
						}
					}
					if (i >= $scope.fsGridConfig.options.data.length) {
						$parse($scope.fsGridConfig.model).assign($scope.ngModel, labelValue);
					}
				}
				function setTypeaheadModel(newValue) {
					var i;
					if (!$scope.fsGridConfig.options || !$scope.fsGridConfig.options.data || !$scope.ngModel) {
						if(!$parse('fsGridConfig._typeaheadModel')($scope)) {
							$parse('fsGridConfig._typeaheadModel').assign($scope, newValue);
						}
						return;
					}
					for (i = 0; i< $scope.fsGridConfig.options.data.length; i++) {
						if ($scope.fsGridConfig.options.data[i].value == $scope.ngModel[$scope.fsGridConfig.model]) {
							$parse('fsGridConfig._typeaheadModel').assign($scope, $scope.fsGridConfig.options.data[i].label);
							break;
						}
					}
					if(!$parse('fsGridConfig._typeaheadModel')($scope)) {
						$parse('fsGridConfig._typeaheadModel').assign($scope, newValue);
					}
				}
				content = $templateCache.get('fs/layout/template/fsTypeahead.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-sensitive ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsSensitive.$inject = [ '$templateCache', '$compile', '$parse' ];
	function fsSensitive($templateCache, $compile, $parse) {
		return {
			restrict : 'EA',
			require : [ '^fsGrid', '^^?form', 'fsSensitive' ],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, watcher, watchers=[];
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options), gridOptionListener);
					watchers.push(watcher);
				}
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options;
				}
				content = $templateCache.get('fs/layout/template/fsSensitive.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-money ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsMoney.$inject = [ '$templateCache', '$compile', 'storageService', '$parse', 'modalService' ];
	function fsMoney($templateCache, $compile, storageService, $parse, modalService) {
		return {
			restrict : 'EA',
			require : [ '^fsGrid', '^^?form', 'fsMoney' ],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, formatInfo = storageService.getFormatInfo(), watcher, watchers = [];
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				$scope.handlePaste = function($event) {
					var i, charCode, text = $event.clipboardData ? $event.clipboardData.getData('text/plain') : null;
					if (text) {
						if($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.allowNegative && text.indexOf('-') == 0) {
							text = text.substring(1, text.length);
						}
						for (i = 0; i < text.length; i++) {
							charCode = text.charCodeAt(i);
							if (text.indexOf('.') >= 0 && text.indexOf('.') != i && charCode == 46) {
								modalService.openErrorDialogs({
									messages : [ 'error.clipboard.invalid' ]
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
							if ((charCode > 57 || charCode < 48) && charCode != 46) {
								modalService.openErrorDialogs({
									messages : [ 'error.clipboard.invalid' ]
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
						}
					}
				};
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options), gridOptionListener);
					watchers.push(watcher);
				}
				$scope.fsGridConfig.placeholder = formatInfo[$scope.fsGridConfig.format];
				watcher = $scope.$watch($attrs.ngModel + '.' + $scope.fsGridConfig.format, function(newValue) {
					if(newValue == $scope.fsGridConfig.format) return;
					//$scope.fsGridConfig.format = newValue || $scope.fsGridConfig.format;
					$scope.fsGridConfig.placeholder = formatInfo[newValue || $scope.fsGridConfig.format] || formatInfo['defaultMoney'];
				});
				watchers.push(watcher);
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options;
				}
				content = $templateCache.get('fs/layout/template/fsMoney.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-number ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsNumber.$inject = [ '$templateCache', '$compile', 'storageService', '$parse', 'modalService' ];
	function fsNumber($templateCache, $compile, storageService, $parse, modalService) {
		return {
			restrict : 'EA',
			require : [ '^fsGrid', '^^?form', 'fsNumber' ],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, formatInfo = storageService.getFormatInfo(), watcher, watchers = [];
				$scope.fsGridConfig.placeholder = formatInfo[$scope.fsGridConfig.format] || $scope.fsGridConfig.format;
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				$scope.handlePaste = function($event) {
					var i, charCode, text = $event.clipboardData ? $event.clipboardData.getData('text/plain') : null;
					if (text) {
						if($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.allowNegative && text.indexOf('-') == 0) {
							text = text.substring(1, text.length);
						}
						for (i = 0; i < text.length; i++) {
							charCode = text.charCodeAt(i);
							if (text.indexOf('.') >= 0 && text.indexOf('.') != i && charCode == 46) {
								modalService.openErrorDialogs({
									messages : [ 'error.clipboard.invalid' ]
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
							if ((charCode > 57 || charCode < 48) && charCode != 46) {
								modalService.openErrorDialogs({
									messages : [ 'error.clipboard.invalid' ]
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
						}
					}
				};
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options), gridOptionListener);
					watchers.push(watcher);
				}
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options;
				}
				content = $templateCache.get('fs/layout/template/fsNumber.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-percentage ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsPercentage.$inject = [ '$templateCache', '$compile', 'storageService', 'modalService' ];
	function fsPercentage($templateCache, $compile, storageService, modalService) {
		return {
			restrict : 'EA',
			require : [ '^fsGrid', '^^?form', 'fsPercentage' ],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, formatInfo = storageService.getFormatInfo(), watcher, watchers = [];
				$scope.fsGridConfig.placeholder = formatInfo[$scope.fsGridConfig.format] || $scope.fsGridConfig.format;
				initComponent(ctrls[2], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				$scope.handlePaste = function($event) {
					var i, charCode, text = $event.clipboardData ? $event.clipboardData.getData('text/plain') : null;
					if (text) {
						if($scope.fsGridConfig && $scope.fsGridConfig.options && $scope.fsGridConfig.options.allowNegative && text.indexOf('-') == 0) {
							text = text.substring(1, text.length);
						}
						for (i = 0; i < text.length; i++) {
							charCode = text.charCodeAt(i);
							if (text.indexOf('.') >= 0 && text.indexOf('.') != i && charCode == 46) {
								modalService.openErrorDialogs({
									messages : [ 'error.clipboard.invalid' ]
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
							if ((charCode > 57 || charCode < 48) && charCode != 46) {
								modalService.openErrorDialogs({
									messages : [ 'error.clipboard.invalid' ]
								});
								$event.returnValue = false;
								$event.preventDefault();
								$event.stopPropagation();
								return false;
							}
						}
					}
				};
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options), gridOptionListener);
					watchers.push(watcher);
				}
				function gridOptionListener(options) {
					$scope.fsGridConfig.options = options;
				}
				content = $templateCache.get('fs/layout/template/fsPercentage.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-lookup-text ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsLookupText.$inject = [ '$templateCache', '$compile', '$log', '$parse', 'modalService' ];
	function fsLookupText($templateCache, $compile, $log, $parse, modalService) {
		return {
			restrict : 'EA',
			require : [ '^fsGrid', 'fsLookupText', '^^?uiGrid' ],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, watcher, watchers = [], log = $log.getInstance('fsLookupText');;
				initComponent(ctrls[1], ctrls[0]);
				$scope._contextMenuOptions = [ 
				    [ 'Remove',
				      function() {
						$parse($scope.fsGridConfig.model).assign($scope.ngModel, null);
					  }, 
					  function() {
						return $parse($scope.fsGridConfig.model)($scope.ngModel);
					  } 
					] 
				];
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options), gridOptionListener);
					watchers.push(watcher);
				} else {
					buildOpenCustomDialogsFunction();
				}
				watcher = $scope.$watch($attrs.ngModel, function(ngModel) {
					var boundTo, options;
					if (angular.isString($scope.fsGridConfig.options)) {
						options = $scope.fsGridConfig.appScope.$eval($scope.fsGridConfig.options);
						boundTo = options ? options.boundTo : null;
					} else if (angular.isObject($scope.fsGridConfig.options)) {
						boundTo = $scope.fsGridConfig.options.boundTo;
					}
					if (boundTo) {
						if (ngModel == null || ngModel == undefined || ngModel == '') {
							if (angular.isString(boundTo)) {
								$parse(boundTo).assign($scope.ngModel, null);
							} else {
								angular.forEach(boundTo, function(value, key) {
									if (value != $scope.fsGridConfig.model) {
										$parse(value).assign($scope.ngModel, null);
									}
								});
							}
						}
					}
				});
				watchers.push(watcher);
				function gridOptionListener(options) {
					if (options) {
						$scope.fsGridConfig.options = options;
						buildOpenCustomDialogsFunction();
					}
				}
				function buildOpenCustomDialogsFunction() {
					if ($scope.fsGridConfig.options) {
						//init manualInput value
						$scope.fsGridConfig.options.manualInput = ($scope.fsGridConfig.options.manualInput == undefined) ? true : $scope.fsGridConfig.options.manualInput;
						$scope.openCustomDialogs = function() {
							var params = {};
							angular.forEach($scope.fsGridConfig.options.params, function(parameterExpression, parameterKey) {
								params[parameterKey] = extractParamater(parameterExpression)
							});
							modalService.openCustomDialogs({
								dialogsModule : $scope.fsGridConfig.options.id,
								size : $scope.fsGridConfig.options.size,
								params : params,
								btnCallback : {
									'ok' : okCallback,
									'close' : closeCallback
								}
							});
						};
					}
				}
				function extractParamater(parameterExpression) {
					var value, appScope = ($scope.uiGrid ? $scope.uiGrid.appScope : ctrls[0].getGridAppScope());
					if (!angular.isString(parameterExpression)) {
						value = parameterExpression;
					} else if(parameterExpression == '<this'){
						if ($scope.ngModel) {
							value = $scope.ngModel;
						} else {
							value = appScope;
						}
					} else if (parameterExpression && parameterExpression.charAt(0) == '<') {
						parameterExpression = parameterExpression.substr(1);
						value = $parse(parameterExpression)($scope.ngModel);
						if (value == undefined) {
							value = $parse(parameterExpression)(appScope);
						}
					} else {
						value = parameterExpression;
					}
					return value;
				}
				function okCallback(lookupResult) {
					var appScope = (ctrls[0].getGridAppScope() || ctrls[2].grid.appScope), callback, result = {}, boundTo = $scope.fsGridConfig.options.boundTo;
					if (!lookupResult || lookupResult.length == 0) {
						modalService.openErrorDialogs({
							messages : [ 'title.dialogs.info.norecsel' ]
						});
						return false;
					}
					if (boundTo && angular.isArray(lookupResult) && lookupResult.length == 1) {
						result = lookupResult[0];
						if (angular.isString(boundTo)) {
							$parse(boundTo).assign($scope.ngModel, result);
						} else {
							angular.forEach(boundTo, function(value, key) {
								$parse(value).assign($scope.ngModel, $parse(key)(result));
							});
						}
					}
					if ($scope.fsGridConfig.options.callback) {
						if (angular.isString($scope.fsGridConfig.options.callback.ok)) {
							callback = appScope.$eval($scope.fsGridConfig.options.callback.ok);
							if (!callback) {
								log.debug("cannot found the callback function:" + $scope.fsGridConfig.options.callback.ok);
							} else {
								callback(lookupResult, $scope.ngModel);
							}
						} else if (angular.isFunction($scope.fsGridConfig.options.callback.ok)) {
							$scope.fsGridConfig.options.callback.ok(lookupResult, $scope.ngModel);
						}
					}
					if ($scope.fsGridConfig.options.okEvent) {
						var resultEntity = {
							returnEntities: lookupResult,
							currentEntity : $scope.ngModel
						}
						appScope.$emit(($scope.fsGridConfig.model + '_OK').toUpperCase(), resultEntity);
					}
				}
				function closeCallback(lookupResult) {
					var appScope = (ctrls[0].getGridAppScope() || ctrls[2].grid.appScope);
					if ($scope.fsGridConfig.options.callback) {
						if (angular.isString($scope.fsGridConfig.options.callback.close)) {
							callback = appScope.$eval($scope.fsGridConfig.options.callback.close);
							if (!callback) {
								log.debug("cannot found the callback function:" + $scope.fsGridConfig.options.callback.close);
							} else {
								callback(lookupResult, $scope.ngModel);
							}
						} else if (angular.isFunction($scope.fsGridConfig.options.callback.close)) {
							$scope.fsGridConfig.options.callback.close(lookupResult, $scope.ngModel);
						}
					}
					if ($scope.fsGridConfig.options.cancelEvent) {
						var resultEntity = {
							returnEntities: lookupResult,
							currentEntity : $scope.ngModel
						};
						appScope.$emit(($scope.fsGridConfig.model + '_CANCEL').toUpperCase(), resultEntity);
					}
				}
				content = $templateCache.get('fs/layout/template/fsLookupText.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}
	
	//<fs-lookup-multiple ng-model="vm.company" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"/>
	fsLookupMultiple.$inject = [ '$templateCache', '$compile', '$parse', '$log', 'modalService', '$filter' ];
	function fsLookupMultiple($templateCache, $compile, $parse, $log, modalService, $filter) {
		return {
			restrict : 'EA',
			require : [ '^fsGrid', 'fsLookupMultiple', '^^?uiGrid' ],
			scope: {
				ngModel : '=ngModel',
				fsVerifyModel : '<fsVerifyModel',
				fsGridConfig : '<fsGridConfig'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var content, watcher, watchers = [], log = $log.getInstance('fsLookupMultiple');;
				initComponent(ctrls[1], ctrls[0]);
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				if (angular.isString($scope.fsGridConfig.options)) {
					watcher = watchGridScope(ctrls[0], $parse($scope.fsGridConfig.options), gridOptionListener);
					watchers.push(watcher);
				} else {
					buildRemoveCallbackFunction();
					buildOpenCustomDialogsFunction();
				}
				watcher = $scope.$watch($attrs.ngModel, modelChanged);
				watchers.push(watcher);
				function gridOptionListener(options) {
					if (options) {
						$scope.fsGridConfig.options = options;
						buildRemoveCallbackFunction();
						buildOpenCustomDialogsFunction();
					}
				}
				function modelChanged(ngModel) {
					var boundTo;
					if (!$scope.fsGridConfig.options){
						return;
					}
					boundTo = $scope.fsGridConfig.options.boundTo;
					if (boundTo) {
						if (ngModel == null || ngModel == undefined || ngModel == '') {
							if (angular.isString(boundTo)) {
								$parse(boundTo).assign($scope.ngModel, null);
							} else {
								angular.forEach(boundTo, function(value, key) {
									if (value != $scope.fsGridConfig.model) {
										$parse(value).assign($scope.ngModel, null);
									}
								});
							}
						}
					}
					if ($scope.fsGridConfig.options.callback && $scope.fsGridConfig.options.callback.initOption) {
						if (angular.isString($scope.fsGridConfig.options.callback.initOption)) {
							var callback = $scope.$eval($scope.fsGridConfig.options.callback.initOption);
							if (!callback) {
								log.debug("cannot found the callback function:" + $scope.fsGridConfig.options.callback.initOption);
							} else {
								callback(ngModel, $scope.fsGridConfig.options);
							}
						} else if (angular.isFunction($scope.fsGridConfig.options.callback.initOption)) {
							$scope.fsGridConfig.options.callback.initOption(ngModel, $scope.fsGridConfig.options);
						}
					} else {
						var modelValue = $parse($scope.fsGridConfig.model)($scope.ngModel);
						if (angular.isArray(modelValue)){
							$scope.fsGridConfig.options.data = [];
							angular.forEach(modelValue, function(value){
								$scope.fsGridConfig.options.data.push({
									'value' : value,
									'label' : value
								});
							});
						}
					}
				}
				function buildRemoveCallbackFunction () {
					if ($scope.fsGridConfig.options.callback && $scope.fsGridConfig.options.callback.removeAll && angular.isString($scope.fsGridConfig.options.callback.removeAll)) {
						$scope.removeAllClick = angular.noop;
						watcher = $scope.fsGridConfig.appScope.$watch($scope.fsGridConfig.options.callback.removeAll, function(removeAll) {
							$scope.removeAllClick = function($event) {
								var promise;
								if (!angular.isFunction(removeAll)) {
									_removeAll();
									return;
								}
								promise = (removeAll || angular.noop)($scope.fsGridConfig.options.data);
								if (promise && angular.isFunction(promise.then)) {
									promise.then(function() {
										_removeAll();
									});
								} else {
									_removeAll();
								}
								$scope.fsGridConfig.appScope.$emit('CONTEXT_MENUE_REMOVE_ALL', $scope.fsGridConfig.options.data);
							};
							initRemoveAllOptions();
						});
						watchers.push(watcher);
					} else if ($scope.fsGridConfig.options.callback && angular.isFunction($scope.fsGridConfig.options.callback.removeAll)) {
						$scope.removeAllClick = function($event) {
							var promise = $scope.fsGridConfig.options.callback.removeAll($scope.fsGridConfig.options.data);
							if (promise && angular.isFunction(promise.then)) {
								promise.then(function() {
									_removeAll();
								});
							} else {
								_removeAll();
							}
							$scope.fsGridConfig.appScope.$emit('CONTEXT_MENUE_REMOVE_ALL', $scope.fsGridConfig.options.data);
						};
						initRemoveAllOptions();
					} else {
						$scope.removeAllClick = function($event) {
							_removeAll();
							$scope.fsGridConfig.appScope.$emit('CONTEXT_MENUE_REMOVE_ALL', $scope.fsGridConfig.options.data);
						};
						initRemoveAllOptions();
					}
					function _removeAll() {
						$parse($scope.fsGridConfig.model).assign($scope.ngModel, []);
	                	$scope.fsGridConfig.options.data = [];
	                }
					function initRemoveAllOptions() {
						$scope.removeAllOptions = [ 
          				  [
                               $filter('translate')('label.global.removeall'),
                               $scope.removeAllClick,
                               function() {
                               	return $scope.fsGridConfig.options.data && $scope.fsGridConfig.options.data.length > 0;
                               } 
                            ] 
          				];
	                }
					if ($scope.fsGridConfig.options.callback && $scope.fsGridConfig.options.callback.remove && angular.isString($scope.fsGridConfig.options.callback.remove)) {
						$scope.removeClick = angular.noop;
						watcher = $scope.fsGridConfig.appScope.$watch($scope.fsGridConfig.options.callback.remove, function(remove) {
							$scope.removeClick = function($event) {
								var promise;
								if (!angular.isFunction(remove)) {
									_remove();
									return;
								}
								promise = (remove || angular.noop)($scope.fsGridConfig.fsLookupMultipleModel);
								if (promise && angular.isFunction(promise.then)) {
									promise.then(function() {
										_remove();
									});
								} else {
									_remove();
								}
								$scope.fsGridConfig.appScope.$emit('CONTEXT_MENUE_REMOVE', $scope.fsGridConfig.fsLookupMultipleModel);
							};
							initContextMenuOptions();
						});
						watchers.push(watcher);
					} else if ($scope.fsGridConfig.options.callback && angular.isFunction($scope.fsGridConfig.options.callback.remove)) {
						$scope.removeClick = function($event) {
							var promise = $scope.fsGridConfig.options.callback.remove($scope.fsGridConfig.fsLookupMultipleModel);
							if (promise && angular.isFunction(promise.then)) {
								promise.then(function() {
									_remove();
								});
							} else {
								_remove();
							}
							$scope.fsGridConfig.appScope.$emit('CONTEXT_MENUE_REMOVE', $scope.fsGridConfig.fsLookupMultipleModel);
						};
						initContextMenuOptions();
					} else {
						$scope.removeClick = function($event) {
							_remove();
							$scope.fsGridConfig.appScope.$emit('CONTEXT_MENUE_REMOVE', $scope.fsGridConfig.fsLookupMultipleModel);
						};
						initContextMenuOptions();
					}
					function _remove() {
						var i, j, getter = $parse($scope.fsGridConfig.model), list = getter($scope.ngModel) || [], options = $scope.fsGridConfig.options.data, option = $scope.fsGridConfig.fsLookupMultipleModel;
						for (i = 0; i < option.length; i++) {
							for (j = 0; j < list.length; j++) {
								if (option[i] == list[j]) {
									list.splice(j, 1);
									getter.assign($scope.ngModel, angular.copy(list));
									break;
								}
							}
						}
						for (i = 0; i < option.length; i++) {
							for (j = 0; j < options.length; j++) {
								if (option[i] == options[j].value) {
									options.splice(j, 1);
									break;
								}
							}
						}
						$scope.fsGridConfig.fsLookupMultipleModel = [];
	                }
					function initContextMenuOptions() {
						$scope._contextMenuOptions = [
	         			      [
	         					$filter('translate')('label.global.remove'),
	         					$scope.removeClick,
	         					function() {
	         						return $scope.fsGridConfig.fsLookupMultipleModel && $scope.fsGridConfig.fsLookupMultipleModel.length > 0;
	         					} 
	         				  ] 
         				];
					}
				}
				function buildOpenCustomDialogsFunction() {
					var params = {};
					if ($scope.fsGridConfig.options) {
						$scope.fsGridConfig.fsLookupMultipleModel = $scope.fsGridConfig.fsLookupMultipleModel || [];
						$scope.fsGridConfig.options.data = $scope.fsGridConfig.options.data || [];
						$scope.openCustomDialogs = function() {
							angular.forEach($scope.fsGridConfig.options.params, function(parameterExpression, parameterKey) {
								params[parameterKey] = extractParamater(parameterExpression)
							});
							modalService.openCustomDialogs({
								dialogsModule : $scope.fsGridConfig.options.id,
								params : params,
								btnCallback : {
									'ok' : okCallback,
									'close' : closeCallback
								}
							});
						};
					}
				}
				function extractParamater(parameterExpression) {
					var value, appScope = ($scope.uiGrid ? $scope.uiGrid.appScope : ctrls[0].getGridAppScope());
					if (!angular.isString(parameterExpression)) {
						value = parameterExpression;
					} else if(parameterExpression == '<this'){
						if ($scope.ngModel) {
							value = $scope.ngModel
						} else {
							value = appScope;
						}
					} else if (parameterExpression && parameterExpression.charAt(0) == '<') {
						parameterExpression = parameterExpression.substr(1);
						value = $parse(parameterExpression)($scope.ngModel);
						if (value == undefined) {
							value = $parse(parameterExpression)(appScope);
						}
					} else {
						value = parameterExpression;
					}
					return value;
				}
				function okCallback(lookupResult) {
					var appScope = (ctrls[0].getGridAppScope() || ctrls[2].grid.appScope), option, boundTo = $scope.fsGridConfig.options.boundTo;
					if (!lookupResult || lookupResult.length == 0) {
						modalService.openErrorDialogs({
							messages : [ 'title.dialogs.info.norecsel' ]
						});
						return false;
					}
					if (boundTo && angular.isArray(lookupResult)) {
						var getter = $parse($scope.fsGridConfig.model), modelValue = getter($scope.ngModel) || [], option;
						angular.forEach(lookupResult, function(result) {
							option = {};
							angular.forEach(boundTo, function(value, key) {
								if (value == 'value' || value == 'label') {
									option[value] = $parse(key)(result);
								}
							});
							option.label = option.label || option.value;
							modelValue.push(option.value);
							$scope.fsGridConfig.options.data.push(option);
						});
						getter.assign($scope.ngModel, angular.copy(modelValue));
					}
					if ($scope.fsGridConfig.options.callback) {
						if (angular.isString($scope.fsGridConfig.options.callback.ok)) {
							callback = appScope.$eval($scope.fsGridConfig.options.callback.ok);
							if (!callback) {
								log.debug("cannot found the callback function:" + $scope.fsGridConfig.options.callback.ok);
							} else {
								callback(lookupResult, $scope.fsGridConfig.options);
							}
						} else if (angular.isFunction($scope.fsGridConfig.options.callback.ok)) {
							$scope.fsGridConfig.options.callback.ok(lookupResult, $scope.fsGridConfig.options);
						}
					}
					if ($scope.fsGridConfig.options.okEvent) {
						appScope.$emit(($scope.fsGridConfig.model + '_OK').toUpperCase(), lookupResult);
					}
				}
				function closeCallback(lookupResult) {
					var appScope = (ctrls[0].getGridAppScope() || ctrls[2].grid.appScope);
					if ($scope.fsGridConfig.options.callback) {
						if (angular.isString($scope.fsGridConfig.options.callback.close)) {
							callback = appScope.$eval($scope.fsGridConfig.options.callback.close);
							if (!callback) {
								log.debug("cannot found the callback function:" + $scope.fsGridConfig.options.callback.close);
							} else {
								callback(lookupResult);
							}
						} else if (angular.isFunction($scope.fsGridConfig.options.callback.close)) {
							$scope.fsGridConfig.options.callback.close(lookupResult);
						}
					}
					if ($scope.fsGridConfig.options.cancelEvent) {
						appScope.$emit(($scope.fsGridConfig.model + '_CANCEL').toUpperCase(), lookupResult);
					}
				}
				content = $templateCache.get('fs/layout/template/fsLookupMultiple.html');
				content = content.replace(/MODEL_NAME/g, 'ngModel.' + $scope.fsGridConfig.model);
				content = $compile(angular.element(content))($scope);
				$element.append(content);
			}
		};
	}

	//<fs-buttons fs-buttons-config="vm.buttonGroup"></fs-buttons>
	fsButtons.$inject = [ '$log', '$timeout', '$templateCache', '$compile', '$parse', 'hotkeys' ];
	function fsButtons($log, $timeout, $templateCache, $compile, $parse, hotkeys) {
		function getAppScope(scope) {
			if (!scope) {
				return null;
			}
			if (scope.hasOwnProperty('vm')) {
				return scope;
			} else {
				return getAppScope(scope.$parent);
			}
		}
		return {
			restrict : 'EA',
			scope : true,
			controller : [ '$scope', '$element', '$attrs', '$transclude', function($scope, $element, $attrs, $transclude) {
				var ctrl = this;
				var appScope = getAppScope($scope);
				$element.addClass('fsButtons');
				appScope.registerSearchCallback = function(callback){
					appScope.searchCallback = callback;
				};
				$scope.isActive = function(grid) {
					var active, expression = grid ? grid.active : undefined;
					if (expression == undefined || expression === true) {
						return true;
					} else if (expression === false) {
						return false;
					}
					active = $parse(expression)($scope);
					if (!active && $scope.ngModel !== undefined && grid.model !== undefined && $scope.ngModel[grid.model] !== undefined) {
						delete $scope.ngModel[grid.model];
					}
					return active;
				};
				$scope.isShow = function(grid) {
					var show, expression = grid ? grid.show : undefined;
					if (expression == undefined || expression === true) {
						return true;
					} else if (expression === false) {
						return false;
					}
					show = $parse(expression)($scope);
					return show;
				};
				$scope.isReadonly = function(grid) {
					var readonly, expression = grid ? grid.readonly : undefined;
					if (expression === true) {
						return true;
					} else if (expression == undefined || expression === false) {
						return false;
					}
					readonly = $parse(expression)($scope);
					return readonly;
				};
				ctrl.getButtonsScope = function () {
					return $scope;
				};
			} ],
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var watchers = [], watcher;
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				watcher = $scope.$watch($attrs.fsButtonsConfig, function(fsButtonsConfig) {
					if(fsButtonsConfig == $scope.buttonGroup) return;
					if (fsButtonsConfig) {
						$scope.buttonGroup = fsButtonsConfig;
						$element.addClass(fsButtonsConfig.groupClass);
					}
				});
				watchers.push(watcher);
			},
			templateUrl : 'fs/layout/template/fsButtons.html'
		};
	}
	
	//<fs-button fs-button-config="vm.button" row="row"></fs-button>
	fsButton.$inject = [ '$rootScope', '$log', '$timeout', '$templateCache', '$compile', '$parse', '$q', 'hotkeys', 'modalService' ];
	function fsButton($rootScope, $log, $timeout, $templateCache, $compile, $parse, $q, hotkeys, modalService) {
		return {
			restrict : 'EA',
			require : [ '^?fsButtons', '^^?form', '^?fsGrid', '^^?uiGrid', 'fsButton' ],
			scope : {
				button : '<fsButtonConfig',
				fsGridConfig : '<fsButtonConfig',
				buttonGroup : '@fsButtonGroup',
				uiGrid : '=fsUiGrid',
				uiGridRow : '=fsUiGridRow',
				row : '=fsUiGridRow',
				uiGridCol : '=fsUiGridCol'
			},
			controller : function() {},
			controllerAs : 'formComponentCtrl',
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var appScope, clickCallback, formCtrl = ctrls[1], fsGridCtrl = ctrls[2], uiGridCtrl = ctrls[3], fsButtonCtrl = ctrls[4], topFormCtrl = getTopFormCtrl(formCtrl);
				if (uiGridCtrl) {
					appScope = uiGridCtrl.grid.appScope;
				} else if (fsGridCtrl) {
					appScope = fsGridCtrl.getGridAppScope();
				} else if (ctrls[0]) {
					appScope = ctrls[0].getButtonsScope();
				} else {
					appScope = $scope.button.btnScope;
				}
				$scope.isSearchCollapsed = function() {
					return appScope.isSearchCollapsed();
				};
				$scope.enabled = function(button) {
					var enabled, expression = button ? button.enabled : undefined;
					if (button.disabled === true) return false;
					if (expression == undefined) {
						return true;
					}
					enabled = $parse(expression)(appScope);
					return enabled;
				};
				$scope.btnClick = function ($event) {
					var rowEntity = null;
					if (ctrls[3]) {
						rowEntity = $scope.uiGridRow.entity;
					} else if (ctrls[0]) {
						rowEntity = ctrls[0].getButtonsScope().ngModel;
					} else if (ctrls[2]) {
						rowEntity = ctrls[2].getGridAppScope().ngModel;
					}
					if (angular.isString($scope.button.options.callback.click)) {
						initButton($scope.button.options.callback.click, 'click');
					}
					return $scope.button.options.callback.click(rowEntity, getOldEntity(rowEntity));
				};
				$scope.isActive = function(grid) {
					if (!appScope || !angular.isFunction(appScope.isActive)) {
						if (!ctrls[2] || !angular.isFunction(ctrls[2].isActive)) {
							return true;
						} 
						return ctrls[2].isActive(grid);
					}
					return appScope.isActive(grid);
				};
				$scope.isShow = function(grid) {
					if (!appScope || !angular.isFunction(appScope.isShow)) {
						if (!ctrls[2] || !angular.isFunction(ctrls[2].isShow)) {
							return true;
						} 
						return ctrls[2].isShow(grid);
					}
					return appScope.isShow(grid);
				};
				$scope.isReadonly = function(grid) {
					if (!angular.isFunction(appScope.isReadonly)) {
						if (!angular.isFunction(ctrls[2].isReadonly)) {
							return false;
						}
						return ctrls[2].isReadonly(grid);
					}
					return appScope.isReadonly(grid);
				};
				$scope.button.tooltips = [];
				if(angular.isArray($scope.button.tooltip)){
					$scope.button.tooltips = $scope.button.tooltip;
				}else if(angular.isString($scope.button.tooltip)){
					$scope.button.tooltips[0] = $scope.button.tooltip;
				}
				$scope.button.authorization = appScope ? (appScope.authorization || ((appScope.$stateParams) ? appScope.$stateParams.authorization : null)) : null;
				$scope.button.permission = $scope.button.permission || '*';
				$scope.button.tooltipPlacement = $scope.button.tooltipPlacement || 'top-left';
				$scope.button.type = $scope.button.type || 'submit';
				$scope.button.btnClass = $scope.button.btnClass || "btn-default";
				$scope.button.options.callback = $scope.button.options.callback || {
					'click' : angular.noop
				};
				angular.forEach($scope.button.options.callback, function(callback, event) {
					initButton(callback, event);
				});
				function initButton(callback, event) {
					if($scope.button.type=='search' && event == 'click') {
						$scope.button.options.callback[event] = function() {
							disableButton();
							appScope.toggleSearchGroupCollapsed();
						};
						appScope.registerSearchCallback(($parse(callback)(appScope) || angular.noop));
						if ($scope.button.options.hotKey) {
							var permissions, i, hasPermission = false;
							permissions = $scope.button ? ($scope.button.permission || '*') : '*';
							if (permissions == '*') hasPermission = true;
							permissions = permissions.split(',');
							angular.forEach(permissions, function(value, key) {
								permissions[key] = value.trim();
							});
							for (i = 0; i < permissions.length; i++) {
								if (!$scope.button || ($scope.button.authorization && $scope.button.authorization[permissions[i]])) {
									hasPermission = true;
								}
							}
							
							function btnCallback(){
								if(!$scope.enabled($scope.button)) {
									return;
								}
								if (disableButton()) {
									return;
								}
								var resultCallback = ($parse(callback)(appScope) || angular.noop);
								excuteCallback(resultCallback, arguments);
							};
							if(hasPermission) {
								hotkeys.bindTo($scope).add({
									combo : $scope.button.options.hotKey,
									description : $scope.button.options.hotKey,
									allowIn: ['RADIO', 'INPUT', 'SELECT', 'TEXTAREA'],
									callback : btnCallback
								});
							}
						}
					} else {
						if (angular.isString(callback)) {
							$scope.button.options.callback[event] = function() {
								disableButton();
								excuteCallback(callback, arguments);
							};
						}
					}
					if ($scope.button.type == 'submit' && event == 'click') {
						clickCallback = $scope.button.options.callback[event];
						if (topFormCtrl) {
							$scope.button.options.callback[event] = function() {
								disableButton();
								var promises = validateFormData();
								$q.all(promises).then(function(){
									topFormCtrl.$setSubmitted();
									delete topFormCtrl.formValidation;
									if (topFormCtrl.$invalid && getFormValidateMessages([topFormCtrl])) {
										if (topFormCtrl.errorMessagesDialog && topFormCtrl.errorMessagesDialog.close) {
											topFormCtrl.errorMessagesDialog.close();
										}
										topFormCtrl.errorMessagesDialog = showFormErrorMessages(topFormCtrl);
									} else {
										$rootScope.disableUnloadCheck();
										excuteCallback(callback, arguments);
									}
								});
							};
						} else {
							$scope.button.options.callback[event] = function() {
								disableButton();
								$rootScope.disableUnloadCheck();
								excuteCallback(callback, arguments);
							};
						}
					} else if ($scope.button.type == 'reset') {
						$scope.button.options.callback = $scope.button.options.callback || {};
						clickCallback = $scope.button.options.callback[event];
						$scope.button.options.callback[event] = function() {
							disableButton();
							var formCtrls = getFormCtrls(formCtrl);
							if (topFormCtrl && topFormCtrl._originalNgModelKey) {
								if (topFormCtrl._originalNgModel) {
									angular.copy(topFormCtrl._originalNgModel, $scope.$eval(topFormCtrl._originalNgModelKey));
								} else {
									$parse(topFormCtrl._originalNgModelKey).assign($scope, topFormCtrl._originalNgModel);
								}
							}
							angular.forEach(formCtrls, function (formController){
								formController.$setPristine();
								formController.$setUntouched();
							});
							excuteCallback(callback, arguments);
						};
					}
					if ($scope.button.options.hotKey && $scope.button.type != 'search') {
						var permissions, i, hasPermission = false;
						permissions = $scope.button ? ($scope.button.permission || '*') : '*';
						if (permissions == '*') hasPermission = true;
						permissions = permissions.split(',');
						angular.forEach(permissions, function(value, key) {
							permissions[key] = value.trim();
						});
						for (i = 0; i < permissions.length; i++) {
							if (!$scope.button || ($scope.button.authorization && $scope.button.authorization[permissions[i]])) {
								hasPermission = true;
							}
						}
						function btnCallback() {
							if(!$scope.enabled($scope.button)) {
								return;
							}
							if (disableButton()) {
								return;
							}
							var resultCallback = ($scope.button.options.callback && $scope.button.options.callback.click ? $scope.button.options.callback.click : angular.noop);
							excuteCallback(resultCallback, arguments);
						};
						if(hasPermission) {
							hotkeys.bindTo($scope).add({
								combo : $scope.button.options.hotKey,
								description : $scope.button.options.hotKey,
								allowIn: ['RADIO', 'INPUT', 'SELECT', 'TEXTAREA'],
								callback : btnCallback
							});
						}
					}
				}
				function excuteCallback(callback, args) {
					if (args.length == 0) {
						($parse(callback)(appScope) || angular.noop)();
					} else if (args.length == 1) {
						($parse(callback)(appScope) || angular.noop)(args[0]);	
					} else if (args.length == 2) {
						($parse(callback)(appScope) || angular.noop)(args[0], args[1]);	
					} else if (args.length == 3) {
						($parse(callback)(appScope) || angular.noop)(args[0], args[1], args[2]);	
					} else if (args.length == 4) {
						($parse(callback)(appScope) || angular.noop)(args[0], args[1], args[2], args[3]);	
					} else if (args.length == 5) {
						($parse(callback)(appScope) || angular.noop)(args[0], args[1], args[2], args[3], args[4]);	
					}
				}
				function getOldEntity(entity) {
					var oldData, key;
					if(appScope && ctrls[3]){
						oldData = appScope.$eval(uiGridCtrl.grid.options.oldData);
					} else if(appScope) {
						oldData = appScope.verifyModel;
					}
					if (oldData) {
						for(data in oldData) {
							if(entity.getPrimaryKey == data.getPrimaryKey){
								return data;
							}
						}
					}
					return null;
				}
				function disableButton () {
					if ($scope.button.disabled) {
						return false;
					}
					$scope.button.disabled = true;
					$timeout(function(){
						$scope.button.disabled = false;
					}, 500);
				}
				function validateFormData() {
					var promises = [], defered;
					if (topFormCtrl && topFormCtrl.tabLoaded) {
						if (!topFormCtrl.formValidation) {
							topFormCtrl.formValidation = new Array(topFormCtrl.tabLoaded.length);
						}
						angular.forEach(topFormCtrl.tabLoaded, function(value, index) {
							if (value === false) {
								topFormCtrl.tabLoaded[index] = true;
								defered = $q.defer();
								topFormCtrl.formValidation[index] = {
									validated : false,
									defered : defered
								};
								promises.push(topFormCtrl.formValidation[index].defered.promise);
							} else {
								promises.push($q.resolve());
							}
						});
					} else {
						promises.push($q.resolve());
					}
					return promises;
				}
				function showFormErrorMessages(topFormCtrl) {
					var dialogsInstance, errors = getFormValidateMessages([topFormCtrl]);
					if (errors) {
						dialogsInstance = modalService.openErrorDialogs({
							'title' : '',
							'messages' : errors
						});
					}
					return dialogsInstance;
				}
				function getFormValidateMessages(formCtrls) {
					var errors = {};
					angular.forEach(formCtrls, function(formCtrl) {
						if (formCtrl.validateErrors) {
							angular.merge(errors, formCtrl.validateErrors);
						}
						if (formCtrl._formCtrls) {
							angular.merge(errors, getFormValidateMessages(formCtrl._formCtrls));
						}
					});
					return angular.equals({}, errors) ? null : errors;
				}
			},
			templateUrl : 'fs/layout/template/fsButton.html'
		};
	}
	
	fsGridFilter.$inject = [ '$templateCache', '$compile', '$parse' ];
	function fsGridFilter($templateCache, $compile, $parse) {
		return {
			restrict : 'A',
			require : [ 'ngModel' ],
			/*scope : {
				filter : '<fsGridFilter'
			},*/
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var watcher, watchers = [], ngModelCtrl = ctrls[0];
				function getModelDisplayValue(model, filter) {
					var parse;
					if (filter) {
						parse = $parse(model + ' | ' + filter);
					} else {
						parse = $parse(model);
					}
					return parse($scope);
				}

				watcher = $scope.$watch($attrs.fsGridFilter, function(filter) {
					if (filter) {
						ngModelCtrl.$formatters.push(function(input) {
							var value = input ? getModelDisplayValue($attrs.ngModel, filter) : input;
							return value;
						});
						$element.val(getModelDisplayValue($attrs.ngModel, filter));
					}
				});
				watchers.push(watcher);

				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
			}
		};
	}
	
	fsGridPrefix.$inject = [ '$templateCache', '$compile', '$parse' ];
	function fsGridPrefix($templateCache, $compile, $parse) {
		return {
			restrict : 'A',
			require : [ 'ngModel' ],
			scope : {
				prefix : '<fsGridPrefix'
			},
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var watcher, watchers = [], ngModelCtrl = ctrls[0];
				watcher = $scope.$watch('prefix', function(prefix) {
					if (prefix) {
						ngModelCtrl.$parsers.push(function(input) {
							var value = input ? prefix + input : input;
							$element.val(input);
							return value;
						});
						ngModelCtrl.$formatters.push(function(output) {
							output = output ? output.replace(prefix, '') : output;
							return output;
						});
					}
				});
				watchers.push(watcher);

				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
			}
		};
	}
	
	// <div fs-ui-grid-compare="verifyData" fs-ui-grid="grid" fs-ui-grid-row="row" />
	fsUiGridCompare.$inject = [ '$parse', '$filter' ];
	function fsUiGridCompare($parse, $filter) {
		return {
			priority: -300,
			restrict : 'A',
			require : ['^^uiGrid', 'fsUiGridCompare'],
			scope : false,
			bindToController : {
				verifyData : '@fsUiGridCompare',
				uiGrid : '=fsUiGrid',
				uiGridRow : '=fsUiGridRow',
				row : '=fsUiGridRow',
				uiGridCol : '=fsUiGridCol'
			},
			controller : [ '$scope', '$element', '$attrs', '$transclude', '$parse', 'formatService', function($scope, $element, $attrs, $transclude, $parse, formatService) {
			} ],
			link : function($scope, $element, $attrs, ctrls, $transclude) {
				var watcher, watchers = [], uiGridCtrl = ctrls[0], fsUiGridCompareCtrl = ctrls[1];
				$scope.$on('$destroy', function() {
					while (watchers.length) {
						(watchers.shift() || angular.noop)();
					}
				});
				watcher = $scope.$watch($attrs.fsUiGridCompare, function(verifyModelProperty) {
					if (verifyModelProperty) {
						watchers.push(uiGridCtrl.grid.appScope.$watch(verifyModelProperty, function(verifyModel) {
							var oldRowEntity;
							if (verifyModel) {
								fsUiGridCompareCtrl.verifyModel = verifyModel;
								oldRowEntity = getVerifyModel(fsUiGridCompareCtrl.uiGridRow.entity, fsUiGridCompareCtrl.verifyModel);
								verifyCompare(fsUiGridCompareCtrl, fsUiGridCompareCtrl.uiGridRow.entity, oldRowEntity);
							}
						}));
					}
				});
				watchers.push(watcher);
			}
		};
		function verifyCompare(fsUiGridCompareCtrl, rowEntity, oldRowEntity) {
			var value, oldValue, tooltip, compareResult;
			if (!rowEntity) {
				return;
			}
			if (!fsUiGridCompareCtrl.uiGridRow.compareResult) {
				compareResult = {
					fields : []
				};
				if (rowEntity.dataStatus == 'D') {
					compareResult.isDeleted = true;
				} else if (oldRowEntity) {
					angular.forEach(fsUiGridCompareCtrl.uiGrid.columns, function(column) {
						if (column.colDef.comparable == false || !column.colDef.model) {
							return;
						}
						value = $parse(column.colDef.model)(rowEntity);
						oldValue = $parse(column.colDef.model)(oldRowEntity);
						if (!angular.equals(value, oldValue)) {
							compareResult.isUpdated = true;
							compareResult.fields[column.colDef.model] = {
								'hasChanged' : true
							};
							if (column.colDef.type == 'sensitive') {
								tooltip = [ formatService.formatSst(oldValue, column.colDef.options) ];
							} else if (column.colDef.filter) {
								tooltip = [ $parse(column.colDef.model + ' | ' + column.colDef.filter)(oldRowEntity) ];
							} else if (column.colDef.type == 'select' || column.colDef.type == 'radio' || column.colDef.type == 'checkbox') {
								var tooltip = oldValue;
								if (column.colDef.options) {
									if (angular.isString(column.colDef.options)) {
										column.colDef.options = $parse(column.colDef.options)(fsUiGridCompareCtrl.uiGrid.appScope);
									}
									if (angular.isString(column.colDef.options.data)) {
										column.colDef.options.data = $parse(column.colDef.options.data)(fsUiGridCompareCtrl.uiGrid.appScope);
									}
									for (var i = 0; i < column.colDef.options.data.length; i++) {
										var obj = column.colDef.options.data[i];
										if (obj.value == oldValue) {
											tooltip = $filter('translate')(obj.label);
											break;
										}
									}
								}
								compareResult.tooltip = [ tooltip ];
							} else if (angular.isDate(oldValue)) {
								if (column.colDef.type == 'date') {
									tooltip = [ formatService.formatDate(oldValue, 'date') ];
								} else if (column.colDef.type == 'datetime') {
									tooltip = [ formatService.formatDate(oldValue, 'timestamp') ];
								} else if (column.colDef.type == 'time') {
									tooltip = [ formatService.formatDate(oldValue, 'HH:SS') ];
								} else {
									tooltip = [ formatService.formatDate(oldValue, 'longTimestamp') ];
								}
							} else {
								tooltip = [ angular.isObject(oldValue) ? angular.toJson(oldValue) : oldValue ];
							}
							compareResult.fields[column.colDef.model].tooltip = tooltip;
						}
					});
				} else {
					compareResult.isCreated = true;
					angular.forEach(fsUiGridCompareCtrl.uiGrid.columns, function(column) {
						compareResult.fields[column.colDef.model] = {
							'hasChanged' : true
						};
					});
				}
				fsUiGridCompareCtrl.uiGridRow.compareResult = compareResult;
			}
		}
		function getVerifyModel(rowEntity, verifyModel) {
			var i = 0;
			if (angular.isArray(verifyModel)) {
				for (i = 0; i < verifyModel.length; i++) {
					if (verifyModel[i].primaryKey == rowEntity.primaryKey) {
						return verifyModel[i];
					}
				}
			}
		}
	}

	setupTemplate.$inject = [ '$templateCache' ];
	function setupTemplate($templateCache) {
		var fsTabset = '<uib-tabset active="options.active" type="options.type" vertical="options.vertical" justified="options.justified"><div id="transclude"></div></uib-tabset>\n';
		var fsTab = '<uib-tab index="options.index" heading="options.heading" disable="options.disable" classes="options.classes" select="options.callback.select" deselect="options.callback.deselect"><div ng-if="options.show" id="transclude"></div></uib-tab>\n';
		var fsTable = '<div ui-grid="TABLE_CONFIG" ui-grid-pagination ui-grid-operation ui-grid-selection ui-grid-resize-columns ui-grid-move-columns ui-grid-auto-resize ui-grid-grouping ui-grid-tree-view></div>\n';
		var fsList = ''
			+ '<div class="fsList form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
			+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
			+ '   <div fs-table fs-config="fsGridConfig.gridOptions" fs-verify-model="fsGridConfig.gridOptions.verifyModel" fs-validator-config="fsGridConfig.gridOptions.validatorConfig"/>\n'
			+ '</div>';
		var fsInnerList = ''
			+ '<div class="fsInnerList form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
			+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
			+ '   <div fs-table fs-config="fsGridConfig.gridOptions" fs-verify-model="fsGridConfig.gridOptions.verifyModel" fs-validator-config="fsGridConfig.gridOptions.validatorConfig"/>\n'
			+ '</div>';
		var fsGridLayout = ''
				+ '<div class="panel-group {{fsGridLayoutController.fsGridCalss}}">\n'
				+ '   <div fs-group ng-repeat="groupContainer in fsGridLayoutController.formContainer" ng-model="fsGridLayoutController.ngModel" fs-verify-model="fsGridLayoutController.fsVerifyModel" fs-group-config="groupContainer" fs-validator-config="fsGridLayoutController.fsValidatorConfig"></div>\n'
				+ '</div>\n';
		var fsGroup = ''
				+ '<div class="{{fsGroupController.groupContainer.type != \'anonymous\' ? \'panel panel-default\' : \'\'}}" ng-show="fsGroupController.isActive() && fsGroupController.isShow()">\n'
				+ '   <div ng-if="fsGroupController.groupContainer.type != \'anonymous\' && fsGroupController.groupContainer.type != \'search\'" ng-class="{\'body-collapsed\': fsGroupController.groupContainer.collapsed()}" class="panel-heading {{fsGroupController.groupContainer.groupClass}}" style="cursor: pointer;" ng-click="fsGroupController.groupContainer.toggleGroupCollapsed()">'
				+ '     <span>{{fsGroupController.groupContainer.title|translate}}</span>'
				+ '     <i class="pull-right glyphicon" ng-class="{\'glyphicon-chevron-down\': !fsGroupController.groupContainer.collapsed(), \'glyphicon-chevron-right\': fsGroupController.groupContainer.collapsed()}"></i>'
				+ '   </div>\n'
				+ '   <div class="{{fsGroupController.groupContainer.type != \'anonymous\' ? \'panel-body\' : \'\'}}" uib-collapse="fsGroupController.groupContainer.collapsed()">\n'
				+ '       <div class="row" ng-repeat="rowObj in fsGroupController.groupContainer.rows">'
				+ '           <div fs-grid ng-repeat="gridObject in rowObj" class="{{gridObject.gridClass}}" ng-class="{\'display-none\': !fsGroupController.isActive(gridObject), \'visibility-hidden\': !fsGroupController.isShow(gridObject)}" ng-model="fsGroupController.ngModel" fs-verify-model="fsGroupController.verifyModel" fs-grid-config="gridObject" fs-validator-config="fsGroupController.fsValidatorConfig"></div>\n'
				+ '       </div>\n'
				+ '       <div fs-group ng-repeat="subGroupContainer in fsGroupController.groupContainer.gridGroups" ng-model="fsGroupController.ngModel" fs-verify-model="fsGroupController.verifyModel" fs-group-config="subGroupContainer" fs-validator-config="fsGroupController.fsValidatorConfig"></div>\n'
				+ '       <div class="row" ng-if="fsGroupController.groupContainer.type == \'search\'">\n'
				+ '           <div class="col-sm-12">\n'
				+ '               <button class="btn btn-primary btn-search pull-right" type="button" ng-click="fsGroupController.searchCallback()">{{"button.text.ok"|translate}}</button>\n'
				+ '           </div>\n'
				+ '       </div>\n'
				+ '   </div>\n' 
				+ '</div>\n';
		var fsGrid = '<div ng-class="{\'has-warning\': fsGridController.isChanged() , \'has-error\': fsGridController.showErrors()}"/><ng-include src="fsGridController.getGridTemplateUrl()"/></div>\n';
		var fsColor = ''
				+ '<div class="fsColor form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-class="{\'input-group\': fsGridConfig.prefix}">\n'
				+ '       <span class="input-group-addon" ng-show="fsGridConfig.prefix">{{::fsGridConfig.prefix}}</span>'
				+ '       <input type="text" colorpicker colorpicker-size="150" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" '
				+ '          uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '          tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"'
				+ '          fs-uppercase="fsGridConfig.uppercase" fs-validator="fsGridConfig.rules" placeholder="{{fsGridConfig.placeholder|translate}}" fs-grid-prefix="fsGridConfig.prefix">\n'
				+ '   </div>\n'
				+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
				+ '       <input disabled type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-grid-filter="fsGridConfig.filter">'
				+ '   </div>\n';
				+ '</div>';
		var fsText = ''
				+ '<div uib-dropdown dropdown-append-to-body="isInTable()" class="fsText form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-class="{\'input-group\': (fsGridConfig.prefix || fsGridConfig.isNecessary || formComponentCtrl.isPatternActive())}">\n'
				+ '       <span class="input-group-addon" ng-if="fsGridConfig.prefix">{{::fsGridConfig.prefix}}</span>'
				+ '       <input type="text" id="textInput" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" ng-trim="false"'
				+ '          uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '          tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"'
				+ '          fs-uppercase="fsGridConfig.uppercase" fs-validator="fsGridConfig.rules" placeholder="{{fsGridConfig.placeholder|translate}}" fs-grid-prefix="fsGridConfig.prefix" ng-focus="focus()" ng-blur="blur()">\n'
				+ '       <span ng-if="formComponentCtrl.isPatternActive()" class="input-group-addon" uib-dropdown-toggle><icon class="icon icon-arrow"/></span>\n'
				+ '       <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
				+ '   </div>\n'
				+ '   <ul ng-if="formComponentCtrl.isPatternActive()" ng-init="formComponentCtrl.initTextPosition()" class="dropdown-menu" ng-class="{\'dropdown-table-pattern\':isInTable(), \'dropdown-pattern\':!isInTable()}" uib-dropdown-menu role="menu" aria-labelledby="btn-append-to-body">\n'
				+ '     <li role="menuitem" ng-repeat="item in fsGridConfig.patternOptions.data"><a href="javascript:void(0)" ng-click="formComponentCtrl.insertPattern(item.value)">{{item.label |translate}}</a></li>\n'
				+ '   </ul>\n'
				+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true"  tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
				+ '       <input disabled type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" fs-grid-filter="fsGridConfig.filter">'
				+ '   </div>\n';
				+ '</div>';
		var fsLabel = ''
				+ '<div class="fsLabel form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <div class="control-label" ng-class="fsGridConfig.labelClass" ng-bind-html="fsGridConfig.label | translate | trustedHtml"></div>\n'
				+ '</div>';
		var fsLink = ''
				+ '<div class="fsLink form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <div uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true"  tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
				+ '       <a class="{{::fsGridConfig.inputClass}}" ng-class="fsGridConfig.linkClass" ng-click="linkClick()">MODEL_NAME</a>\n'
				+ '   </div>\n';
				+ '</div>';
		var fsImage = ''
				+ '<div class="fsImage form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <img ng-class="fsGridConfig.imgClass" ng-src="{{::MODEL_NAME | trustedUrl}}"></img>\n'
				+ '</div>';
		var fsPassword = ''
				+ '<div class="fsPassword form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>'
				+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-class="{\'input-password\': true, \'input-group\': fsGridConfig.isNecessary}">\n'
				+ '   <input type="{{isVisible == \'visible\' ? \'text\' : \'password\'}}" class="form-control password-input {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" placeholder="{{fsGridConfig.placeholder|translate}}"'
				+ '        uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">'
				+ '       <div class="password-btn" ng-click="clickVisible()">\n'
				+ '           <i class="icon iconfont icon-{{isVisible}}"></i>\n'
				+ '       </div>\n'
				+ '       <div class="password-btn">\n'
				+ '           <i class="icon iconfont icon-keyboard"></i>\n'
				+ '       </div>\n'
				+ '       <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
				+ '   </div>\n'
				+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
				+ '   <input disabled type="password" fs-validator="fsGridConfig.rules" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" placeholder="{{fsGridConfig.placeholder|translate}}">\n'
				+ '   </div>\n'
				+ '</div>\n';
		var fsRadio = ''
			+ '<div class="fsRadio form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
			+ '   <label class="control-label" ng-if="fsGridConfig.showLabel" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
			+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-class="{\'input-group\': fsGridConfig.isNecessary}">\n'
			+ '   <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">'
			+ '   <ul class="list-inline input-group {{::fsGridConfig.inputClass}}"'
			+ '        uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '       <li ng-repeat="item in fsGridConfig.options.data"><input type="radio" ng-model="MODEL_NAME" ng-value="item.value" ng-checked="item.value==MODEL_NAME"> {{item.label | translate}}</li>\n'
			+ '   </ul>\n'
			+ '   <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
			+ '   </div>\n'
			+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)">\n'
			+ '   <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">'
			+ '   <ul class="list-inline input-group {{::fsGridConfig.inputClass}}"'
			+ '        uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
			+ '       <li ng-repeat="item in fsGridConfig.options.data"><input disabled type="radio" ng-model="MODEL_NAME" ng-value="item.value" ng-checked="item.value==MODEL_NAME"> {{item.label | translate}}</li>\n'
			+ '   </ul>\n' 
			+ '   </div>\n'
			+ '</div>';
		var fsCheckbox = ''
				+ '<div class="fsCheckbox form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" style="overflow:auto;" ng-class="{\'input-group\': fsGridConfig.isNecessary}"><input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">'
				+ '   <ul class="list-inline input-group {{::fsGridConfig.inputClass}}"'
				+ '        uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
				+ '       <li ng-repeat="item in fsGridConfig.options.data">'
				+ '       <input type="checkbox" value="{{::item.value}}" ng-checked="indexOf(item.value) > -1" ng-click="toggleSelection(item.value);clicked();"> <span class="control-label">{{item.label | translate}}</span></li>'
				+ '   </ul>\n'
				+ '   <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
				+ '   </div>\n'
				+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" style="overflow:auto;" ><ul class="list-inline input-group {{::fsGridConfig.inputClass}}"'
				+ '        uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
				+ '       <li ng-repeat="item in fsGridConfig.options.data">'
				+ '       <input disabled type="checkbox" value="{{::item.value}}" ng-checked="indexOf(item.value) > -1"> {{item.label | translate}}</li>'
				+ '   </ul></div>\n' 
				+ '</div>';
		var fsCheckboxGroup = ''
			+ '<div class="fsCheckboxGroup form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
			+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
			+ '   <div class="panel panel-default" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-repeat="group in fsGridConfig.options.groups">\n'
			+ '       <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">'
			+ '       <div ng-class="{\'body-collapsed\': group.isCollapsed}" class="panel-heading" style="cursor: pointer;" ng-click="toggleGroupCollapse(group)">\n'
			+ '           <input type="checkbox" ng-if="group.showHeadCheckbox" ng-model="group.allChecked" ng-checked="group.allChecked" ng-click="toggleSelectionAll($event, group)"> {{group.title | translate}}'
			+ '           <i class="pull-right glyphicon" ng-class="{\'glyphicon-chevron-down\': !group.isCollapsed, \'glyphicon-chevron-right\': group.isCollapsed}"></i>'
			+ '       </div>\n'
			+ '       <div class="panel-body" uib-collapse="group.isCollapsed">\n'
			+ '           <ul ng-repeat="item in group.data" class="list-inline {{::fsGridConfig.inputClass}}" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)">\n'
			+ '               <li>'
			+ '                   <input type="checkbox" value="{{::item.value}}" ng-checked="indexOfModel(item.value) > -1" ng-click="toggleSelectionAll($event, group, item.value)"> {{item.label | translate}}'
			+ '               </li>'
			+ '               <li>'
			+ '                   {{item.desc | translate}}'
			+ '               </li>'
			+ '           </ul>\n'
			+ '       </div>\n'
			+ '   </div>\n'
			+ '   <div class="panel panel-default" ng-if="formComponentCtrl.isReadonly(fsGridConfig)" ng-repeat="group in fsGridConfig.options.groups">\n'
			+ '       <div class="panel-heading" style="cursor: pointer;" ng-click="toggleGroupCollapse(group)">\n'
			+ '           <i class="pull-right glyphicon" ng-class="{\'glyphicon-chevron-down\': !group.isCollapsed, \'glyphicon-chevron-right\': group.isCollapsed}"></i>'
			+ '       </div>\n'
			+ '       <div class="panel-body" uib-collapse="group.isCollapsed">\n'
			+ '           <ul ng-repeat="item in group.data" class="list-inline {{::fsGridConfig.inputClass}}" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)">\n'
			+ '               <li>'
			+ '                   <input disabled type="checkbox" value="{{::item.value}}" ng-checked="indexOfModel(item.value) > -1" ng-click="toggleSelectionAll($event, group, item.value)"> {{item.label | translate}}'
			+ '               </li>'
			+ '           </ul>\n'
			+ '       </div>\n'
			+ '   </div>\n'
			+ '</div>';
		var fsDate = ''
				+ '<div class="fsDate form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <div class="input-group" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)">\n'
				+ '       <input type="text" class="form-control {{::fsGridConfig.inputClass}}" uib-datepicker-popup="{{fsGridConfig.format}}" datepicker-append-to-body="true" alt-input-formats="formComponentCtrl.altInputFormats" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" is-open="_opened"'
				+ '            datepicker-options="_dateOptions" placeholder="{{fsGridConfig.placeholder|translate}}"'
				+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
				+ '       <span class="input-group-btn">\n'
				+ '           <button type="button" class="btn btn-default {{::fsGridConfig.inputClass}}" ng-click="_opened=!_opened">\n'
				+ '               <i class="icon icon-calendar"></i>\n'
				+ '           </button>\n'
				+ '       </span>\n'
				+ '  	  <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
				+ '   </div>\n'
				+ '   <div class="input-group" ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
				+ '       <input disabled type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" uib-datepicker-popup="{{fsGridConfig.format}}" datepicker-options="_dateOptions" is-open="_opened">'
				+ '   </div>\n'
				+ '</div>\n';
		var fsTime = '' 
			+ '<div class="fsTime form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n' 
			+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
			+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig) || formComponentCtrl.isReadonly(fsGridConfig)" ng-class="{\'input-group\': fsGridConfig.isNecessary}" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">'
			+ '       <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">\n'
			+ '       <div uib-timepicker ng-model="MODEL_NAME" show-seconds="fsGridConfig.options.showSeconds" show-spinners="_showSpinners" hour-step="fsGridConfig.options.hourStep" minute-step="fsGridConfig.options.minuteStep" show-meridian="fsGridConfig.options.isMeridian" readonly-input="formComponentCtrl.isReadonly(fsGridConfig)" ng-disabled="formComponentCtrl.isReadonly(fsGridConfig)" ></div>\n'
			+ '  	  <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsDatetime = ''
				+ '<div class="fsDatetime form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <div class="input-group" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)">\n'
				+ '       <input type="text" class="form-control {{::fsGridConfig.inputClass}}" uib-datepicker-popup="{{fsGridConfig.format}}" datepicker-append-to-body="true" alt-input-formats="formComponentCtrl.altInputFormats" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" is-open="_opened"'
				+ '            datepicker-options="_dateOptions" placeholder="{{fsGridConfig.placeholder|translate}}"'
				+ '            uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
				+ '       <div uib-timepicker class="input-group-addon" ng-model="MODEL_NAME" show-seconds="fsGridConfig.options.showSeconds" show-spinners="_showSpinners" hour-step="fsGridConfig.options.hourStep" minute-step="fsGridConfig.options.minuteStep" show-meridian="fsGridConfig.options.isMeridian" readonly-input="formComponentCtrl.isReadonly(fsGridConfig)" ng-disabled="formComponentCtrl.isReadonly(fsGridConfig)" ></div>\n'
				+ '       <span class="input-group-btn">\n'
				+ '           <button type="button" class="btn btn-default {{::fsGridConfig.inputClass}}" ng-click="_opened=!_opened">\n'
				+ '               <i class="icon icon-calendar"></i>\n'
				+ '           </button>\n'
				+ '       </span>\n'
				+ '  	  <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
				+ '   </div>\n'
				+ '   <div class="input-group" ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
				+ '       <div><input disabled type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" uib-datepicker-popup="{{fsGridConfig.format}}" datepicker-options="_dateOptions" fs-validator="fsGridConfig.rules" is-open="_opened"></div>\n'
				+ '       <div uib-timepicker class="input-group-addon" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" show-seconds="showSeconds" show-spinners="_showSpinners" hour-step="fsGridConfig.options.hourStep" minute-step="fsGridConfig.options.minuteStep" show-meridian="fsGridConfig.options.isMeridian" readonly-input="formComponentCtrl.isReadonly(fsGridConfig)" ng-disabled="formComponentCtrl.isReadonly(fsGridConfig)"></div>\n'
				+ '   </div>\n'
				+ '</div>\n';
		var fsCalendarDay = ''
				+ '<div class="fsCalendarDay form-group" ng-repeat="option in _calendarOptions">\n'
				+ '   <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">\n'
				+ '   <div uib-datepicker template-url="CALENDAR_TEMPLATE_URL" ng-model="option._tempDate" class="well well-sm" datepicker-options="option"></div>'
				+ '</div>\n';
		var fsTextarea = ''
				+ '<div class="fsTextarea form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-class="{\'input-group\': fsGridConfig.isNecessary}">\n'
				+ '  	<textarea class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-uppercase="fsGridConfig.uppercase" fs-validator="fsGridConfig.rules" placeholder="{{fsGridConfig.placeholder|translate}}" rows= "{{::fsGridConfig.options.rows}}" cols= "{{::fsGridConfig.options.cols}}"'
				+ '        uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
				+ '	  	</textarea>\n'
				+ '     <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
				+ '   </div>\n'
				+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
				+ '       <textarea disabled class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" fs-validator="fsGridConfig.rules" ng-model="MODEL_NAME" rows= "{{::fsGridConfig.options.rows}}" cols= "{{::fsGridConfig.options.cols}}" fs-grid-filter="fsGridConfig.filter"></textarea>\n'
				+ '   </div>\n'
				+ '</div>\n';
		var fsSelect = '' 
				+ '<div class="fsSelect form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n' 
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-class="{\'input-group\': fsGridConfig.isNecessary}">\n'
				+ '   	<select class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" ng-options="option.value as option.label|translate for option in fsGridConfig.options.data"'
				+ '          uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '          tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}" fs-validator="fsGridConfig.rules">\n' 
				+ '       <option value="" ng-if="fsGridConfig.options.hasEmptyOption"></option>\n'
				+ '   	</select>\n'
				+ '     <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
				+ '   </div>\n'
				+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
				+ '       <select fs-validator="fsGridConfig.rules" disabled class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" ng-options="option.value as option.label|translate for option in fsGridConfig.options.data">\n'
				+ '           <option value="" ng-if="fsGridConfig.options.hasEmptyOption"></option>\n'
				+ '       </select>\n'
				+ '   </div>\n'
				+ '</div>\n';
		var fsTypeahead = ''
				+ '<div class="fsTypeahead form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-class="{\'input-group\': fsGridConfig.isNecessary}">'
				+ '   <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">\n'
				+ '    <input type="text" class="form-control {{::fsGridConfig.inputClass}}" ng-model="fsGridConfig._typeaheadModel" fs-uppercase="fsGridConfig.uppercase" placeholder="{{fsGridConfig.placeholder|translate}}" '
				+ '      uib-typeahead="state.label|translate as state.label|translate for state in fsGridConfig.options.data | filter:{label:$viewValue} | limitTo:fsGridConfig.options.length" '
				+ '      typeahead-editable="true"'
				+ '      typeahead-template-url="fs/layout/template/fsTypeaheadOption.html"'
				+ '      typeahead-show-hint="false" typeahead-min-length="0" '
				+ '      uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '      tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">'
				+ '    <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
				+ '	   </div>\n'
				+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
				+ '       <input disabled type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" placeholder="{{fsGridConfig.placeholder|translate}}" fs-grid-filter="fsGridConfig.filter">'
				+ '   <div>\n'
				+ '</div>\n';
		var fsTypeaheadOption = '<a><span ng-bind-html="match.label | uibTypeaheadHighlight:query"></span></a>\n';
		var fsSensitive = ''
				+ '<div class="fsSensitive form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-class="{\'input-group\': (fsGridConfig.prefix || fsGridConfig.isNecessary)}">\n'
				+ '       <span class="input-group-addon" ng-if="fsGridConfig.prefix">{{::fsGridConfig.prefix}}</span>'
				+ '       <input type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" '
				+ '             uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '             tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"'
				+ '             fs-uppercase="fsGridConfig.uppercase" fs-validator="fsGridConfig.rules" fs-sst-format="fsGridConfig.options" placeholder="{{fsGridConfig.placeholder|translate}}" fs-grid-prefix="fsGridConfig.prefix">\n'
				+ '       <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
				+ '   </div>\n'
				+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
				+ '       <input disabled fs-validator="fsGridConfig.rules" type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-sst-format="{{fsGridConfig.options}}">'
				+ '   </div>\n' 
				+ '</div>\n';
		var fsMoney = ''
				+ '<div class="fsMoney form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-class="{\'input-group\': fsGridConfig.isNecessary}">'
				+ '     <input type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" fs-money-format="{{fsGridConfig.format}}" placeholder="{{fsGridConfig.placeholder|translate}}"'
				+ '        ng-paste="handlePaste($event)"'
				+ '        uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
				+ '     <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
				+ '   </div>'
				+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
				+ '       <input disabled type="text" fs-validator="fsGridConfig.rules" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-money-format="{{fsGridConfig.format}}">'
				+ '   </div>\n' 
				+ '</div>\n';
		var fsNumber = ''
				+ '<div class="fsNumber form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-class="{\'input-group\': fsGridConfig.isNecessary}">'
				+ '     <input type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" fs-num-format="{{fsGridConfig.format}}" placeholder="{{fsGridConfig.placeholder|translate}}"'
				+ '        ng-paste="handlePaste($event)"'
				+ '        uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
				+ '     <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
				+ '   </div>'
				+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
				+ '       <input disabled type="text" fs-validator="fsGridConfig.rules" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-num-format="{{fsGridConfig.format}}">'
				+ '   </div>\n' 
				+ '</div>\n';
		var fsPercentage = ''
				+ '<div class="fsPercentage form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" class="input-group">'
				+ '      <input type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" fs-num-format="{{fsGridConfig.format}}" placeholder="{{fsGridConfig.placeholder|translate}}"'
				+ '        ng-paste="handlePaste($event)"'
				+ '        uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n'
				+ '		 <span class="input-group-addon">%</span>\n'
				+ '      <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
				+ '   </div>'
				+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}" class="input-group">\n' 
				+ '       <input disabled type="text" fs-validator="fsGridConfig.rules" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-num-format="{{fsGridConfig.format}}">\n'
				+ '		 <span class="input-group-addon">%</span>\n'
				+ '   </div>\n' 
				+ '</div>\n';
		var fsLookupText = ''
				+ '<div class="fsLookupText form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
				+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '   <div class="input-group" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)">\n'
				+ '       <input ng-disabled="!fsGridConfig.options.manualInput || fsGridConfig.filter == \'encode\' " type="text" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-uppercase="fsGridConfig.uppercase" fs-validator="fsGridConfig.rules" placeholder="{{fsGridConfig.placeholder|translate}}"'
				+ '        uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '            tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}" fs-grid-filter="fsGridConfig.filter">\n'
				+ '       <span class="input-group-btn">\n'
				+ '           <button type="button" context-menu="_contextMenuOptions" class="btn btn-default {{::fsGridConfig.inputClass}}" ng-click="openCustomDialogs()">\n'
				+ '               <i class="glyphicon glyphicon-search"></i>\n'
				+ '           </button>\n'
				+ '       </span>\n'
				+ '       <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
				+ '   </div>\n'
				+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
				+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
				+ '       <input disabled type="text" fs-validator="fsGridConfig.rules" class="form-control {{::fsGridConfig.inputClass}}" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" placeholder="{{fsGridConfig.placeholder|translate}}" fs-grid-filter="fsGridConfig.filter">'
				+ '   </div>\n' 
				+ '</div>\n';
		var fsMultipleSelect = '' 
			+ '<div class="fsMultipleSelect form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n' 
			+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
			+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-class="{\'input-group\': fsGridConfig.isNecessary}">'
			+ '  	 <select class="form-control {{::fsGridConfig.inputClass}}" multiple size="fsGridConfig.options.length" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" ng-options="option.value as option.label|translate for option in fsGridConfig.options.data"'
			+ '          uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '          tooltip-trigger="\'focus\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
			+ '       <option value="" ng-if="fsGridConfig.options.hasEmptyOption"></option>\n'
			+ '  	 </select>\n'
			+ '      <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
			+ '   </div>\n'
			+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
			+ '       <select disabled class="form-control input-sm in" multiple size="fsGridConfig.options.length" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules" ng-options="option.value as option.label|translate for option in fsGridConfig.options.data">\n'
			+ '           <option value="" ng-if="fsGridConfig.options.hasEmptyOption"></option>\n'
			+ '       </select>\n'
			+ '   </div>\n'
			+ '</div>\n';
		var fsLookupMultiple = ''
			+ '<div class="fsLookupMultiple form-group" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n'
			+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
			+ '   <div class="input-group" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)">\n'
			+ '       <input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">'
			+ '  	  <select ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" context-menu="_contextMenuOptions" class="form-control {{::fsGridConfig.inputClass}}" multiple size="fsGridConfig.options.length" ng-model="fsGridConfig.fsLookupMultipleModel" ng-options="option.value as option.label|translate for option in fsGridConfig.options.data"'
			+ '          uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '          tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}"></select>\n' 
			+ '       <span class="input-group-btn">\n'
			+ '           <button type="button" context-menu="removeAllOptions" class="btn btn-default {{::fsGridConfig.inputClass}}" ng-click="openCustomDialogs()">\n'
			+ '               <i class="glyphicon glyphicon-search"></i>\n'
			+ '           </button>\n'
			+ '       </span>\n'
			+ '       <span ng-if="!fsGridConfig.showLabel && fsGridConfig.isNecessary" class="required input-group-addon"></span>\n'
			+ '   </div>\n'
			+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)" uib-tooltip-template="fsGridConfig.tooltipTemplateUrl" tooltip-enable="!!formComponentCtrl.getTooltipMessage()" tooltip-class="{{::fsGridConfig.tooltipClass}}"'
			+ '        tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="{{::fsGridConfig.tooltipPlacement}}">\n' 
			+ '       <select disabled class="form-control input-sm in" multiple size="fsGridConfig.options.length" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" ng-options="option.value as option.label|translate for option in fsGridConfig.options.data"></select>\n'
			+ '   </div>\n' 
			+ '</div>\n';
		var fsButtons = ''
				+ '<div class="panel panel-default {{::buttonGroup.groupClass}}" ng-if="buttonGroup.buttons">\n'
				+ '	   <div class="fsButton form-group" ng-if="fsGridConfig && isActive(fsGridConfig)" ng-show="isShow(fsGridConfig)">\n'
				+ '    	  	<label class="control-label" ng-if="fsGridConfig && fsGridConfig.label"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '			<div>\n'
				+ '         	<fs-button fs-button-config="button" fs-button-group="true" fs-ui-grid="grid" fs-ui-grid-row="row" fs-ui-grid-col="col" ng-repeat="button in buttonGroup.buttons"/>\n'
				+ '		 	</div>\n'
				+ '	   </div>\n'
				+ '    <div class="panel-heading" ng-if="!fsGridConfig">\n'
				+ '         <fs-button fs-button-config="button" fs-button-group="true" fs-ui-grid="grid" fs-ui-grid-row="row" fs-ui-grid-col="col" ng-repeat="button in buttonGroup.buttons"/>\n'
				+ '    </div>\n'
				+ '</div>\n';
		var fsButton = ''
				+ '<div class="fsButton form-group" ng-if="!buttonGroup && fsGridConfig && isActive(fsGridConfig)" ng-show="isShow(fsGridConfig)">\n'
				+ '    <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
				+ '    <div fs-permission="button">\n'
				+ '        <button class="btn {{::button.btnClass}} {{::button.type}}" type="{{::button.type != \'search\' ? button.type : \'button\'}}" ng-mousedown="stopMouseEvent($event)" ng-mouseleave="stopMouseEvent($event)" ng-mouseover="stopMouseEvent($event)" ng-mouseup="stopMouseEvent($event)"'
				+ '          ng-disabled="!enabled(button)"'
				+ '          uib-tooltip="{{button.options.hotKey.toUpperCase() || button.tooltips[0]|translate }}" tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="button.tooltipPlacement"'
				+ '          ng-click="btnClick($event)">\n'
				+ '             <span class="icon icon-{{button.options.icon || button.name}} pull-left"></span>'
				+ '             <span>{{button.text|translate}}</span>'
				+ '             <span ng-if="button.type==\'search\'" ng-class="{\'dropup\': !isSearchCollapsed()}">\n'
				+ '                 <span class="icon icon-arrow"></span>\n'
				+ '             </span>\n'
				+ '        </button>'
				+ '    </div>\n'
				+ '</div>\n'
				+ '<div fs-permission="button" style="display:inline" ng-if="buttonGroup" ng-show="isShow(fsGridConfig)">\n'
				+ '<button class="btn {{::button.btnClass}} {{::button.type}}" type="{{::button.type != \'search\' ? button.type : \'button\'}}" ng-mousedown="stopMouseEvent($event)" ng-mouseleave="stopMouseEvent($event)" ng-mouseover="stopMouseEvent($event)" ng-mouseup="stopMouseEvent($event)"'
				+ '    ng-disabled="!enabled(button)"'
				+ '    uib-tooltip="{{button.options.hotKey.toUpperCase() || button.tooltips[0]|translate}}" tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true"'
				+ '    tooltip-placement="button.tooltipPlacement"'
				+ '    ng-click="btnClick($event)">\n'
				+ '     <span class="icon icon-{{button.options.icon || button.name}} pull-left"></span>\n'
				+ '     <span>{{button.label|translate}}</span>'
				+ '     <span ng-if="button.type==\'search\'" ng-class="{\'dropup\': !isSearchCollapsed()}">\n'
				+ '         <span class="icon icon-arrow"></span>\n'
				+ '     </span>\n'
				+ '</button>\n'
				+ '</div>';
		var fsFileUpload = ''
			+ '<div class="fsFileUpload form-group fsFileUpload" ng-show="formComponentCtrl.isActive(fsGridConfig) && formComponentCtrl.isShow(fsGridConfig)">\n' 
			+ '   <label class="control-label" ng-if="fsGridConfig.showLabel"><span ng-class="{required: fsGridConfig.isRequired}"></span><span>{{fsGridConfig.label|translate}}</span>:</label>\n'
			+ '   <label class="btn btn-default btn-file" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)">'
			+ '    {{"label.file.choosefile"|translate}}'
			+ '    <span class="icon icon-upload pull-left"></span>'
			+ '    <input type="file" id="fileSelect" ng-show="false" ng-if="!_fileUploadOptions.isMultiple" nv-file-select uploader="_uploader" options="_fileUploadOptions" accept="_fileUploadOptions.mimeTypes">'
			+ '    <input type="file" id="fileSelect" ng-show="false" ng-if="_fileUploadOptions.isMultiple" nv-file-select uploader="_uploader" options="_fileUploadOptions" multiple accept="_fileUploadOptions.mimeTypes">'
			+ '   </label>\n'
			+ '   <select class="form-control {{::fsGridConfig.inputClass}}" ng-if="!fsGridConfig.options.hiddenMimeOptions" ng-disabled="formComponentCtrl.isReadonly(fsGridConfig)" style="display:inline-block;width:auto;min-width:100px;"'
			+ '       ng-if="!fsGridConfig.options.hiddenMimeTypes && !formComponentCtrl.isReadonly(fsGridConfig)" ng-model="fsGridConfig.options.mimeTypes"'
			+ '       ng-options="option.value as option.label|translate for option in fsGridConfig.options.data"></select>'
			+ '	  <fs-button fs-button-config="button" fs-button-group="true" ng-repeat="button in fsGridConfig.buttonsConfig.buttons" class="fsButton" ng-if="fsGridConfig.buttonsConfig && fsGridConfig.buttonsConfig.buttons"/>'
			+ '   <div ng-if="!formComponentCtrl.isReadonly(fsGridConfig)"><input type="hidden" name="{{::fsGridConfig.model}}" ng-model="MODEL_NAME" fs-validator="fsGridConfig.rules">'
			+ '   <table class="table" ng-show="_uploader.queue.length > 0 || MODEL_NAME.length > 0">'
			+ '    	<thead>'
			+ '    		<tr>'
			+ '    			<th width="40%">{{"label.file.name"|translate}}</th>'
			/*+ '    			<th width="15%">{{"label.file.size"|translate}}</th>'*/
			+ '    			<th>{{"label.file.progress"|translate}}</th>'
			+ '    			<th width="20%">{{"label.file.actions"|translate}}</th>'
			+ '    		</tr>'
			+ '    	</thead>'
			+ '    	<tbody>'
			+ '          <tr ng-repeat="modelItem in MODEL_NAME">'
			+ '    			<td><strong>{{:: modelItem.docFileName }}</strong></td>'
			/*+ '    			<td>&nbsp</td>'*/
			+ '    			<td ng-if="!formComponentCtrl.isReadonly(fsGridConfig)">'
			+ '    				<div class="progress" style="margin-bottom: 0;">'
			+ '                     <uib-progressbar type="success"><b>100%</b></uib-progressbar>'
			+ '    				</div>'
			+ '    			</td>'
			+ '    			<td>'
			+ '    				<button type="button" class="btn btn-default btn-xs" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-click="__isOpen=false;_remove(modelItem)"'
			+ '                     tooltip-is-open="__isOpen" uib-tooltip="{{\'label.file.remove\'|translate}}" tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="top">'
			+ '    					<span class="icon icon-remove"></span>'
			+ '    				</button>'
			+ '    				<button type="button" class="btn btn-default btn-xs" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-click="__isOpen=false;_download($event, modelItem)"'
			+ '                     tooltip-is-open="__isOpen" uib-tooltip="{{\'label.file.download\'|translate}}" tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="top">'
			+ '    					<span class="icon icon-download"></span>'
			+ '    				</button>'
			+ '    				<button type="button" class="btn btn-default btn-xs" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)" ng-click="__isOpen=false;_open($event, modelItem)"'
			+ '                     tooltip-is-open="__isOpen" uib-tooltip="{{\'label.file.open\'|translate}}" tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="top">'
			+ '    					<span class="icon icon-view"></span>'
			+ '    				</button>'
			+ '    			</td>'
			+ '    		</tr>'
			+ '          <tr ng-repeat="item in _uploader.queue">'
			+ '    			<td><strong>{{:: item.file.name }}</strong></td>'
			/*+ '    			<td>{{:: item.file.size/1024/1024|numberFormat:\'money2\' }} MB</td>'*/
			+ '    			<td ng-if="!formComponentCtrl.isReadonly(fsGridConfig)">'
			+ '    				<div class="progress" style="margin-bottom: 0;">'
			+ '                     <uib-progressbar type="success" max="\'100\'" value="item.progress"><span>{{item.progress}}/100</span></uib-progressbar>'
			+ '    				</div>'
			+ '    			</td>'
			+ '    			<td>'
			+ '    				<button type="button" disabled class="btn btn-default btn-xs" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)"'
			+ '                     uib-tooltip="{{\'label.file.remove\'|translate}}" tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="top">'
			+ '    					<span class="icon icon-remove"></span>'
			+ '    				</button>'
			+ '    				<button type="button" disabled class="btn btn-default btn-xs" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)"'
			+ '                     uib-tooltip="{{\'label.file.download\'|translate}}" tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="top">'
			+ '    					<span class="icon icon-download"></span>'
			+ '    				</button>'
			+ '    				<button type="button" disabled class="btn btn-default btn-xs" ng-if="!formComponentCtrl.isReadonly(fsGridConfig)"'
			+ '                     uib-tooltip="{{\'label.file.open\'|translate}}" tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="top">'
			+ '    					<span class="icon icon-view"></span>'
			+ '    				</button>'
			+ '    			</td>'
			+ '    		</tr>'
			+ '    	</tbody>'
			+ '    </table></div>'
			+ '   <div ng-if="formComponentCtrl.isReadonly(fsGridConfig)"><table class="table">'
			+ '    	<thead>'
			+ '    		<tr>'
			+ '    			<th width="80%">{{"label.file.name"|translate}}</th>'
			/*+ '    			<th>{{"label.file.size"|translate}}</th>'*/
			+ '    			<th>{{"label.file.actions"|translate}}</th>'
			+ '    		</tr>'
			+ '    	</thead>'
			+ '    	<tbody>'
			+ '          <tr ng-repeat="item in MODEL_NAME">'
			+ '    			<td><strong>{{item.docFileName }}</strong></td>'
			/*+ '    			<td>{{item.size/1024/1024|numberFormat:\'money2\' }} MB</td>'*/
			+ '    			<td>'
			+ '    				<button type="button" class="btn btn-default btn-xs" ng-if="formComponentCtrl.isReadonly(fsGridConfig)" ng-click="__isOpen=false;_download($event, item)"'
			+ '                     tooltip-is-open="__isOpen" uib-tooltip="{{\'label.file.download\'|translate}}" tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="top">'
			+ '    					<span class="icon icon-download"></span>'
			+ '    				</button>'
			+ '    				<button type="button" class="btn btn-default btn-xs" ng-if="formComponentCtrl.isReadonly(fsGridConfig)" ng-click="__isOpen=false;_open($event, item)"'
			+ '                     tooltip-is-open="__isOpen" uib-tooltip="{{\'label.file.open\'|translate}}" tooltip-trigger="\'mouseenter\'" tooltip-append-to-body="true" tooltip-placement="top">'
			+ '    					<span class="icon icon-view"></span>'
			+ '    				</button>'
			+ '    			</td>'
			+ '    		</tr>'
			+ '    	</tbody>'
			+ '    </table></div>'
			+ '</div>\n';
		var tooltipTpl = '' 
				+ '<ul class="fstooltip list-inline">\n' 
				+ '   <li ng-repeat="message in formComponentCtrl.tooltip">{{message | translate }}</li>\n'
				+ '</ul>\n';
		$templateCache.put('fs/layout/template/fsTabset.html', fsTabset);
		$templateCache.put('fs/layout/template/fsTab.html', fsTab);
		$templateCache.put('fs/layout/template/fsTable.html', fsTable);
		$templateCache.put('fs/layout/template/fsList.html', fsList);
		$templateCache.put('fs/layout/template/fsInnerList.html', fsInnerList);
		$templateCache.put('fs/layout/template/fsGridLayout.html', fsGridLayout);
		$templateCache.put('fs/layout/template/fsGroup.html', fsGroup);
		$templateCache.put('fs/layout/template/fsGrid.html', fsGrid);
		$templateCache.put('fs/layout/template/fsColor.html', fsColor);
		$templateCache.put('fs/layout/template/fsLabel.html', fsLabel);
		$templateCache.put('fs/layout/template/fsLink.html', fsLink);
		$templateCache.put('fs/layout/template/fsImage.html', fsImage);
		$templateCache.put('fs/layout/template/fsText.html', fsText);
		$templateCache.put('fs/layout/template/fsPassword.html', fsPassword);
		$templateCache.put('fs/layout/template/fsRadio.html', fsRadio);
		$templateCache.put('fs/layout/template/fsCheckbox.html', fsCheckbox);
		$templateCache.put('fs/layout/template/fsCheckboxGroup.html', fsCheckboxGroup);
		$templateCache.put('fs/layout/template/fsDate.html', fsDate);
		$templateCache.put('fs/layout/template/fsTime.html', fsTime);
		$templateCache.put('fs/layout/template/fsDatetime.html', fsDatetime);
		$templateCache.put('fs/layout/template/fsCalendarDay.html', fsCalendarDay);
		$templateCache.put('fs/layout/template/fsTextarea.html', fsTextarea);
		$templateCache.put('fs/layout/template/fsSelect.html', fsSelect);
		$templateCache.put('fs/layout/template/fsMultipleSelect.html', fsMultipleSelect);
		$templateCache.put('fs/layout/template/fsTypeahead.html', fsTypeahead);
		$templateCache.put('fs/layout/template/fsTypeaheadOption.html', fsTypeaheadOption);
		$templateCache.put('fs/layout/template/fsSensitive.html', fsSensitive);
		$templateCache.put('fs/layout/template/fsMoney.html', fsMoney);
		$templateCache.put('fs/layout/template/fsNumber.html', fsNumber);
		$templateCache.put('fs/layout/template/fsPercentage.html', fsPercentage);
		$templateCache.put('fs/layout/template/fsLookupText.html', fsLookupText);
		$templateCache.put('fs/layout/template/fsLookupMultiple.html', fsLookupMultiple);
		$templateCache.put('fs/layout/template/fsButtons.html', fsButtons);
		$templateCache.put('fs/layout/template/fsButton.html', fsButton);
		$templateCache.put('fs/layout/template/tooltipTpl.html', tooltipTpl);
		$templateCache.put('fs/layout/template/fsFileUpload.html', fsFileUpload);
		
		$templateCache.put('fs/layout/template/list.html', '<div fs-list ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"></div>');
		$templateCache.put('fs/layout/template/innerList.html', '<div fs-inner-list ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"></div>');
		$templateCache.put('fs/layout/template/color.html', '<div fs-color ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"></div>');
		$templateCache.put('fs/layout/template/label.html', '<div fs-label ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"></div>');
		$templateCache.put('fs/layout/template/link.html', '<div fs-link ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"></div>');
		$templateCache.put('fs/layout/template/image.html', '<div fs-image ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig"></div>');
		$templateCache.put('fs/layout/template/text.html', '<div fs-text ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/password.html', '<div fs-password ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/date.html', '<div fs-date ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/time.html', '<div fs-time ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/datetime.html', '<div fs-datetime ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/calendarDay.html', '<div fs-calendar-day ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/checkbox.html', '<div fs-checkbox ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/checkboxGroup.html', '<div fs-checkbox-group ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/radio.html', '<div fs-radio ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/textarea.html', '<div fs-textarea ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/sensitive.html', '<div fs-sensitive ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/typeahead.html', '<div fs-typeahead ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/select.html', '<div fs-select ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/multipleSelect.html', '<div fs-multiple-select ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/number.html', '<div fs-number ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/percentage.html', '<div fs-percentage ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/money.html', '<div fs-money ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/fileUpload.html', '<div fs-file-upload ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/lookupText.html', '<fs-lookup-text ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/lookupImage.html', '<div fs-lookup-image ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/lookupMultiple.html', '<div fs-lookup-multiple ng-model="ngModel" fs-verify-model="verifyModel" fs-grid-config="fsGridConfig" fs-validator-config="fsValidatorConfig"></div>');
		$templateCache.put('fs/layout/template/button.html', '<div fs-button fs-button-config="fsGridConfig" fs-ui-grid="grid" fs-ui-grid-row="row" fs-ui-grid-col="col" row="row"></div>');
		$templateCache.put('fs/layout/template/buttons.html', '<div fs-buttons fs-buttons-config="fsGridConfig.buttonsConfig"></div>');
		$templateCache.put('fs/template/tabs/tab.html', ''
				+ '<li ng-class="[{active: active, disabled: disabled}, classes]" class="uib-tab nav-item">\n'
				+ '  <a href ng-click="select($event)" class="nav-link" fs-tab-heading-transclude>{{heading}}</a>\n' 
				+ '</li>\n'
				+ '');
		$templateCache.put('fs/template/tabs/tabset.html', ''
				+ '<div>\n'
				+ '  <ul class="nav nav-{{tabset.type || \'tabs\'}}" ng-class="{\'nav-stacked\': vertical, \'nav-justified\': justified}" ng-transclude></ul>\n'
				+ '  <div class="tab-content">\n' 
				+ '    <div class="tab-pane"\n' 
				+ '         ng-repeat="tab in tabset.tabs"\n'
				+ '         ng-if="tabset.loaded[tab.index]" ng-class="{active: tabset.active === tab.index}"\n' 
				+ '         fs-tab-content-transclude="tab">\n' 
				+ '    </div>\n'
				+ '  </div>\n' 
				+ '</div>\n' 
				+ '');
		$templateCache.put('fs/layout/template/fsDashboardPanel.html', '');
	}
	
	function initComponent(componentController, fsGridController){
		componentController.focus = function() {
			return fsGridController.focus();
		};
		componentController.blur = function() {
			return fsGridController.blur();
		};
		componentController.isChanged = function () {
			return fsGridController.isChanged();
		};
		componentController.getTooltipMessage = function (){
			componentController.tooltip = fsGridController.getTooltipMessage();
			return componentController.tooltip;
		};
		componentController.isActive = function(grid) {
			return fsGridController.isActive(grid);
		};
		componentController.isShow = function(grid) {
			return fsGridController.isShow(grid);
		};
		componentController.isReadonly = function(grid) {
			return fsGridController.isReadonly(grid);
		};
	}
	function watchGridScope(fsGridController, watchExp, listener) {
		return fsGridController.getGridAppScope().$watch(watchExp, function(newVal, oldVal, scope){
			listener(newVal, oldVal, scope);
		});
	}
	function getTopFormCtrl(formCtrl) {
		return (formCtrl && formCtrl.$$parentForm && formCtrl.$$parentForm[formCtrl.$name]) ? getTopFormCtrl(formCtrl.$$parentForm) : formCtrl;
	}
	function registerFormCtrls(topFormCtrl, formCtrl) {
		if (!formCtrl || topFormCtrl == formCtrl)
			return;
		if(topFormCtrl._formCtrls && topFormCtrl._formCtrls[formCtrl.$$parentForm.$name + "-" + formCtrl.$name]){
			return;
		}
		topFormCtrl._formCtrls = topFormCtrl._formCtrls || {};
		topFormCtrl._formCtrls[formCtrl.$$parentForm.$name + "-" + formCtrl.$name] = formCtrl;
		if (formCtrl.$$parentForm && formCtrl.$$parentForm[formCtrl.$name]) {
			registerFormCtrls(formCtrl.$$parentForm, formCtrl);
		}
	}
	
	function destoryFormCtrls(formCtrls, currentFormCtrl) {
		angular.forEach(formCtrls, function(formCtrl) {
			if (formCtrl && formCtrl._formCtrls && formCtrl._formCtrls[currentFormCtrl.$$parentForm.$name + "-" + currentFormCtrl.$name]) {
				delete formCtrl._formCtrls[currentFormCtrl.$$parentForm.$name + "-" + currentFormCtrl.$name];
				destoryFormCtrls(formCtrl._formCtrls, currentFormCtrl);
			}
		});
	}
	
	function getFormCtrls(formCtrl) {
		if (!formCtrl)
			return null;
		var topFormCtrl = getTopFormCtrl(formCtrl);
		return topFormCtrl._formCtrls;
	}
	function prepareGridRules($parse, gridConfig, validatorConfig) {
		if (!gridConfig || !gridConfig.model || !(validatorConfig || gridConfig.ruleInfos)) {
			return;
		}
		var commonRules, modelName = gridConfig.model, dynamicParamRuleInfos = {};
		dynamicParamRuleInfos.ruleInfos = gridConfig.ruleInfos;
		commonRules = $parse(modelName)(validatorConfig);
		gridConfig.rules = commonRules || dynamicParamRuleInfos;
		if (gridConfig.rules) {
			gridConfig.rules.field = gridConfig.model;
			angular.forEach(gridConfig.rules.ruleInfos, function(rule) {
				if (gridConfig.filter == 'encode') {
					rule.needFilterModelValue = true;
				}
				if (angular.lowercase(rule.ruleName) == 'required' || (rule.conditionValue && (angular.lowercase(rule.ruleName) == 'dependent'))) {
					gridConfig.isRequired = true;
					if(gridConfig.dynamicParameter) gridConfig.isNecessary = true;
				}
			});
		}
		gridConfig.hasPreparedGridRules = true;
	}
	function buildCompareResult($parse, $filter, formatService, fsGridLayoutController, gridConfig, value, oldValue) {
		if(gridConfig.comparable == false || !gridConfig.model) {
			return;
		}
		var compareResult = {};
		compareResult.hasChanged = true;
		if (gridConfig.type == 'sensitive') {
			oldValue = oldValue || '';
			compareResult.tooltip = [ formatService.formatSst(oldValue, gridConfig.options) ];
		} else if(gridConfig.filter){
			compareResult.tooltip = [$parse(gridConfig.model + ' | ' + gridConfig.filter)(fsGridLayoutController.fsVerifyModel)];
		} else if (gridConfig.type == 'select' || gridConfig.type == 'radio' || gridConfig.type == 'checkbox') {
			oldValue = oldValue || '';
			var tooltip = oldValue;
			if(gridConfig.options) {
				if(angular.isString(gridConfig.options)) {
					gridConfig.options = $parse(gridConfig.options)(gridConfig.appScope);
				}
				if(angular.isString(gridConfig.options.data)) {
					gridConfig.options.data = $parse(gridConfig.options.data)(gridConfig.appScope);
				}
				if(gridConfig.options.data && gridConfig.options.data.length) {
					for (var i = 0; i < gridConfig.options.data.length; i++) {
						var obj = gridConfig.options.data[i];
						if (obj.value == oldValue) {
							tooltip = $filter('translate')(obj.label);
							break;
						}
					}
				}
			}
			compareResult.tooltip = [ tooltip ];
		} else if(angular.isDate(oldValue)) {
			if (gridConfig.type == 'date') {
				compareResult.tooltip = [formatService.formatDate(oldValue, 'date')];
			} else if (gridConfig.type == 'datetime') {
				compareResult.tooltip = [formatService.formatDate(oldValue, 'timestamp')];
			} else if (gridConfig.type == 'time') {
				compareResult.tooltip = [formatService.formatDate(oldValue, 'HH:SS')];
			} else {
				compareResult.tooltip = [formatService.formatDate(oldValue, 'longTimestamp')];
			}
		} else {
			compareResult.tooltip = [angular.isObject(oldValue) ? angular.toJson(oldValue) : oldValue];
		}
		return compareResult;
	}
})(angular);